生成填字游戏的算法

发布于 2024-07-23 17:02:03 字数 86 浏览 10 评论 0原文

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

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

Given a list of words, how would you go about arranging them into a crossword grid?

It wouldn't have to be like a "proper" crossword puzzle which is symmetrical or anything like that: basically just output a starting position and direction for each word.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(13

诗酒趁年少 2024-07-30 17:02:04

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

解决约束问题的方法有很多(一般属于 NPC 复杂性类)。

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

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

高效简约或许才是终极智慧!

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

抛开所见即所得构建器部分,编译器概要如下:

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

  2. 在用户构建的网格上查找词槽(即网格词)(例如,位于 x,y 处的词,长度为 L,水平或垂直)(复杂度 O(N) )< /p>

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

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

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

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

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 中实现的。 该算法可以轻松并行化,以便同时产生多个(不同的)解决方案

希望这有帮助

Although this is an older question, will attempt an answer based on similar work i have done.

There are many approaches to solving constraint problems (which generallay are in NPC complexity class).

This is related to combinatorial optimization and constraint programming. In this case the constraints are the geometry of the grid and the requirement that words are unique etc..

Randomization/Annealing approaches can also work (although within the proper setting).

Efficient simplicity might just be the ultimate wisdom !

The requirements were for a more or less complete crossword compiler and (visual WYSIWYG) builder.

Leaving aside the WYSIWYG builder part, the compiler outline was this:

  1. Load the available wordlists (sorted by word length, ie 2,3,..,20)

  2. Find the wordslots (ie grid words) on the user-constructed grid (eg word at x,y with length L, horizontal or vertical) ( complexity O(N) )

  3. Compute the intersecting points of the grid words (that need to be filled) ( complexity O(N^2) )

  4. Compute the intersections of the words in the wordlists with the various letters of the alphabet used (this allows to search for matching words by using a template eg. Sik Cambon thesis as used by cwc ) ( complexity O(WL*AL) )

Steps .3 and .4 allow to do this task:

a. The intersections of the grid words with themselves enable to create a "template" for trying to find matches in the associated wordlist of available words for this grid word (by using the letters of other intersecting words with this word which are already filled at a certain step of the algorithm)

b. The intersections of the words in a wordlist with the alphabet enable to find matching (candidate) words that match a given "template" (eg 'A' in 1st place and 'B' in 3rd place etc..)

So with these data structures implemented the algorithm used was sth like this:

NOTE: if the grid and the database of words are constant the previous steps can just be done once.

  1. First step of the algorithm is select an empty wordslot (grid word) at random and fill it with a candidate word from its associated wordlist (randomization enables to produce different solutons in consecutive executions of the algorithm) ( complexity O(1) or O(N) )

  2. For each still empty word slots (that have intersections with already filled wordslots), compute a constraint ratio (this can vary, sth simple is the number of available solutions at that step) and sort the empty wordslots by this ratio ( complexity O(NlogN) or O(N) )

  3. Loop through the empty wordslots computed at previous step and for each one try a number of cancdidate solutions (making sure that "arc-consistency is retained", ie grid has a solution after this step if this word is used) and sort them according to maximum availability for next step (ie next step has a maximum possible solutions if this word is used at that time in that place, etc..) ( complexity O(N*MaxCandidatesUsed) )

  4. Fill that word (mark it as filled and go to step 2)

  5. If no word found that satisfies the criteria of step .3 try to backtrack to another candidate solution of some previous step (criteria can vary here) ( complexity O(N) )

  6. If backtrack found, use the alternative and optionally reset any already filled words that might need reset (mark them as unfilled again) ( complexity O(N) )

  7. If no backtrack found, the no solution can be found (at least with this configuration, initial seed etc..)

  8. Else when all wordlots are filled you have one solution

This algorithm does a random consistent walk of the solution tree of the problem. If at some point there is a dead end, it does a backtrack to a previous node and follow another route. Untill either a solution found or number of candidates for the various nodes are exhausted.

The consistency part makes sure that a solution found is indeed a solution and the random part enables to produce different solutions in different executions and also on the average have better performance.

PS. all this (and others) were implemented in pure JavaScript (with parallel processing and WYSIWYG) capability

PS2. The algorithm can be easily parallelized in order to produce more than one (different) solution at the same time

Hope this helps

无尽的现实 2024-07-30 17:02:04

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

您会惊讶地发现,像这样的蒙特卡罗方法经常有效。

Why not just use a random probabilistic approach to start with. Start with a word, and then repeatedly pick a random word and try to fit it into the current state of the puzzle without breaking the constraints on the size etc.. If you fail, just start all over again.

You will be surprised how often a Monte Carlo approach like this works.

动听の歌 2024-07-30 17:02:04

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

Here is some JavaScript code based on nickf's answer and Bryan's Python code. Just posting it in case someone else needs it in 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();
}
俏︾媚 2024-07-30 17:02:04

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

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

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

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

I'd generate two numbers: Length and Scrabble score. Assume that a low Scrabble score means it's easier to join on (low scores = lots of common letters). Sort the list by length descending and Scrabble score ascending.

Next, just go down the list. If the word doesn't cross with an existing word (check against each word by their length and Scrabble score, respectively), then put it into the queue, and check the next word.

Rinse and repeat, and this should generate a crossword.

Of course, I'm pretty sure that this is O(n!) and it's not guaranteed to complete the crossword for you, but perhaps somebody can improve it.

蝶…霜飞 2024-07-30 17:02:04

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

因此,我的总体想法是:创建某种相对强力的方法,如此处描述的一些方法来创建低密度交叉,并用字典单词填充空白。

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

I've been thinking about this problem. My sense is that to create a truly dense crossword, you cannot hope that your limited word list is going to be enough. Therefore, you might want to take a dictionary and place it into a "trie" data structure. This will allow you to easily find words that fill the left over spaces. In a trie, it is fairly efficient to implement a traversal that, say, gives you all words of the form "c?t".

So, my general thinking is: create some sort of relatively brute force approach as some described here to create a low-density cross, and fill in the blanks with dictionary words.

If anyone else has taken this approach, please let me know.

计㈡愣 2024-07-30 17:02:04

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

0.!/usr/bin/python

  1. a. allwords.sort(key=len,reverse=True)

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

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

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

  4. 迭代单词,如并使用可用空间长度定义最大字长:
    <代码>
    温度=[]
    对于范围内的 w_size( len( w_space ), 2, -1 ) :
    # t
    for w in [ if len(word) == w_size ] 中的字对字:
    #
    如果 w 不在 temp 中并且 putTheWord( w, w_space ) :
    #
    临时.追加(w)

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

    w_space=['c','.','a','.','.','.'] # 而点是空白单元格 
    
      # 转换多个“.”   正则表达式的“.*” 
    
      模式 = r''.join( [ x.letter for x in w_space ] ) 
      模式=pattern.strip('.') +'.*' 如果模式[-1] == '.'   其他模式 
    
      prog = re.compile( 模式, re.U | re.I ) 
    
      如果 prog.match( w ) : 
          # 
          如果 prog.match( w ).group() == w : 
              # 
              返回真 
      
  6. 在每个成功使用的单词后,改变方向。
    当所有单元格都被填充时循环,或者你用完单词或者通过迭代限制然后:

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

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

  1. 通过填写的难易程度和一些估计计算来制定评分系统。
    为当前填字游戏给出分数,并通过将其附加到来缩小以后的选择范围
    如果您的评分系统满足分数,则制作填字游戏列表。

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

    在第一次迭代

通过使用更多参数,速度可以大大提高。

I was playing around crosswords generator engine, and I found this the most important :

0.!/usr/bin/python

  1. a. allwords.sort(key=len, reverse=True)

    b. make some item/object like cursor which will walk around matrix for easy orientation
    unless you want to iterate by random choice later on.

  2. the first, pick up first pair and place them across and down from 0,0 ;
    store the first one as our current crossword 'leader'.

  3. move cursor by order diagonal or random with greater diagonal probability to next empty cell

  4. iterate over the words like and use free space length to define max word length :

    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. to compare word against free space I used i.e. :

    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. after each successfully used word, change direction.
    Loop while all cells are filled OR you run out of words OR by limit of iterations then :

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

...and iterate again new crossword.

  1. Make the scoring system by easiness of filling, and some estimation calcs.
    Give score for the current crossword and narrow later choice by append it into
    list of made crosswords if the score is satisfied by your scoring system.

  2. After first iteration session iterate again from the list of made crosswords to finish the job.

By using more parameters speed can be improved by a huge factor.

趴在窗边数星星i 2024-07-30 17:02:04

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

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

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

`

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

`

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

    <代码>一个
    放弃
    能力
    有能力的
    流产
    关于
    多于
    国外
    缺席
    绝对
    绝对地
    ...

现在,CSP 的定义和求解如下:

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

`

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

在此处输入图像描述

这是另一张包含孟加拉语(孟加拉语)语言单词列表的图片:

< a href="https://i.sstatic.net/RveHK.gif" rel="nofollow noreferrer">输入图像描述这里

This one appears as a project in the AI CS50 course from Harvard. The idea is to formulate the crossword generation problem as a constraint satisfaction problem and solve it with backtracking with different heuristics to reduce the search space.

To start with we need couple of input files:

  1. The structure of the crossword puzzle (which looks like the following one, e.g., where the '#' represents the characters not to be filled with and '_' represents the characters to be filled with)

`

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

`

  1. An input vocabulary (word list / dictionary) from which the candidate words will be chosen (like the one shown follows).

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

Now the CSP is defined and to be solved as follows:

  1. Variables are defined to have values (i.e., their domains) from the list of words (vocabulary) provided as input.
  2. Each variable gets represented by a 3 tuple: (grid_coordinate, direction, length) where the coordinate represents the start of the corresponding word, direction can be either of horizontal or vertical and the length is defined as the length of the word the variable will be assigned to.
  3. The constraints are defined by the structure input provided: for example, if a horizontal and a vertical variable has a common character, it will represented as an overlap (arc) constraint.
  4. Now, the node consistency and AC3 arc consistency algorithms can be used to reduce the domains.
  5. Then backtracking to obtain a solution (if one exists) to the CSP with MRV (minimum remaining value), degree etc. heuristics can be used to select the next unassigned variable and heuristics like LCV (least constraining value) can be used for domain-ordering, to make the search algorithm faster.

The following shows the output that was obtained using an implementation of the CSP solving algorithm:

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

`

The following animation shows the backtracking steps:

enter image description here

Here is another one with a Bangla (Bengali) language word-list:

enter image description here

唠甜嗑 2024-07-30 17:02:04

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

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

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

I would get an index of each letter used by each word to know possible crosses. Then I would choose the largest word and use it as base. Select the next large one and cross it. Rinse and repeat. It's probably an NP problem.

Another idea is creating a genetic algorithm where the strength metric is how many words you can put in the grid.

The hard part I find is when to know a certain list cannot possibly be crossed.

樱花细雨 2024-07-30 17:02:04

jQuery 填字游戏谜题生成器和游戏

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

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

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

我使用的算法的目的:

  1. 最小化网格中不可用方块的数量,如下所示尽可能多。
  2. 尽可能多地混合单词。
  3. 计算速度极快。

演示生成的填字游戏。

我将描述我使用的算法:

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

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

  3. 开始填字游戏时,第一个单词块位于填字游戏的左上角。

    开始填字

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

jQuery Crossword Puzzle Generator and Game

I have coded up a JavaScript/jQuery solution to this problem :

Sample Demo: http://www.earthfluent.com/crossword-puzzle-demo.html

Source Code: https://github.com/HoldOffHunger/jquery-crossword-puzzle-generator

The intent of the algorithm I used:

  1. Minimize the number of the unusable squares in the grid as much as possible.
  2. Have as many inter-mixed words as possible.
  3. Compute in an extremely fast time.

Demonstration of a generated crossword puzzle.

I will describe the algorithm I used:

  1. Group the words together according to those that share a common letter.

  2. From these groups, build sets of a new data structure ("word blocks"), which is a primary word (that runs through all other words) and then the other words (that run through the primary word).

  3. Start out the crossword puzzle with the very first of these word blocks in the very top-left position of the crossword puzzle.

  4. For the rest of the word blocks, starting from the right-bottom most position of the crossword puzzle, move upward and leftward, until there are no more available slots to fill. If there are more empty columns upwards than leftwards, move upwards, and vice versa.

谢绝鈎搭 2024-07-30 17:02:03

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

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

这是一个可行但通常很糟糕的填字游戏。 我对上面的基本配方做了一些修改,以获得更好的结果。

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

I came up with a solution which probably isn't the most efficient, but it works well enough. Basically:

  1. Sort all the words by length, descending.
  2. Take the first word and place it on the board.
  3. Take the next word.
  4. Search through all the words that are already on the board and see if there are any possible intersections (any common letters) with this word.
  5. If there is a possible location for this word, loop through all the words that are on the board and check to see if the new word interferes.
  6. If this word doesn't break the board, then place it there and go to step 3, otherwise, continue searching for a place (step 4).
  7. Continue this loop until all the words are either placed or unable to be placed.

This makes a working, yet often quite poor crossword. There were a number of alterations I made to the basic recipe above to come up with a better result.

  • At the end of generating a crossword, give it a score based on how many of the words were placed (the more the better), how large the board is (the smaller the better), and the ratio between height and width (the closer to 1 the better). Generate a number of crosswords and then compare their scores and choose the best one.
    • Instead of running an arbitrary number of iterations, I've decided to create as many crosswords as possible in an arbitrary amount of time. If you only have a small word list, then you'll get dozens of possible crosswords in 5 seconds. A larger crossword might only be chosen from 5-6 possibilities.
  • When placing a new word, instead of placing it immediately upon finding an acceptable location, give that word location a score based on how much it increases the size of the grid and how many intersections there are (ideally you'd want each word to be crossed by 2-3 other words). Keep track of all the positions and their scores and then choose the best one.
说不完的你爱 2024-07-30 17:02:03

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

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

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

到最后,你就会得到一个不错的纵横字谜或单词搜索谜题,因为它们大致相同。 它往往运行得相当好,但如果您有任何改进建议,请告诉我。 更大的网格运行速度呈指数级下降; 更大的单词列表呈线性。 更大的单词列表也更有可能获得更好的单词位置数。

I just recently wrote my own in Python. You can find it here: http://bryanhelmig.com/python-crossword-puzzle-generator/. It doesn't create the dense NYT style crosswords, but the style of crosswords you might find in a child's puzzle book.

Unlike a few algorithms I found out there that implemented a random brute-force method of placing words like a few have suggested, I tried to implement a slightly smarter brute-force approach at word placement. Here's my process:

  1. Create a grid of whatever size and a list of words.
  2. Shuffle the word list, and then sort the words by longest to shortest.
  3. Place the first and longest word at the upper left most position, 1,1 (vertical or horizontal).
  4. Move onto next word, loop over each letter in the word and each cell in the grid looking for letter to letter matches.
  5. When a match is found, simply add that position to a suggested coordinate list for that word.
  6. Loop over the suggested coordinate list and "score" the word placement based on how many other words it crosses. Scores of 0 indicate either bad placement (adjacent to existing words) or that there were no word crosses.
  7. Back to step #4 until word list is exhausted. Optional second pass.
  8. We should now have a crossword, but the quality can be hit or miss due to some of the random placements. So, we buffer this crossword and go back to step #2. If the next crossword has more words placed on the board, it replaces the crossword in the buffer. This is time limited (find the best crossword in x seconds).

By the end, you have a decent crossword puzzle or word search puzzle, since they are about the same. It tends to run rather well, but let me know if you have any suggestions on improvement. Bigger grids run exponentially slower; bigger word lists linearly. Bigger word lists also have a much higher chance at better word placement numbers.

不如归去 2024-07-30 17:02:03

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

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

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

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

一旦单词/线索文件达到一定大小(并且每天为该客户添加 50-100 条线索),很少有需要对每个填字游戏进行超过两到三个手动修复的情况。

I actually wrote a crossword generation program about ten years ago (it was cryptic but the same rules would apply for normal crosswords).

It had a list of words (and associated clues) stored in a file sorted by descending usage to date (so that lesser-used words were at the top of the file). A template, basically a bit-mask representing the black and free squares, was chosen randomly from a pool that was provided by the client.

Then, for each non-complete word in the puzzle (basically find the first blank square and see if the one to the right (across-word) or the one underneath (down-word) is also blank), a search was done of the file looking for the first word that fitted, taking into account the letters already in that word. If there was no word that could fit, you just marked the whole word as incomplete and moved on.

At the end would be some uncompleted words which the compiler would have to fill in (and add the word and a clue to the file if desired). If they couldn't come up with any ideas, they could edit the crossword manually to change constraints or just ask for a total re-generation.

Once the word/clue file got up to a certain size (and it was adding 50-100 clues a day for this client), there was rarely a case of more than two or three manual fix ups that had to be done for each crossword.

維他命╮ 2024-07-30 17:02:03

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

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)

This algorithm creates 50 dense 6x9 arrow crosswords in 60 seconds. It uses a word database (with word+tips) and a board database (with pre-configured boards).

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

A bigger word database decreases generation time considerably and some kind of boards are harder to fill! Bigger boards require more time to be filled correctly!


Example:

Pre-Configured 6x9 Board:

(# means one tip in one cell, % means two tips in one cell, arrows not shown)

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

Generated 6x9 Board:

# 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 

Tips [line,column]:

[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)
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文