クロスワードを生成するアルゴリズム


122

単語のリストを前提として、それらをクロスワードグリッドに配置するにはどうすればよいでしょうか。

これは、対称的な「適切な」クロスワードパズルのようなものである必要はありません。基本的に、各単語の開始位置と方向を出力するだけです。

回答:


62

私はおそらく最も効率的ではないソリューションを考え出しましたが、それは十分に機能します。基本的に:

  1. すべての単語を長さの降順でソートします。
  2. 最初の単語を取り、ボードに配置します。
  3. 次の言葉を取りなさい。
  4. ボード上にすでにあるすべての単語を検索し、この単語との共通部分(一般的な文字)の可能性があるかどうかを確認します。
  5. この単語の可能性のある場所がある場合は、ボード上のすべての単語をループして、新しい単語が干渉するかどうかを確認します。
  6. この単語がボードを壊さない場合は、そこに配置してステップ3に進みます。それ以外の場合は、場所の検索を続けます(ステップ4)。
  7. すべての単語が配置されるか配置できないまで、このループを続けます。

これは機能しますが、かなり貧弱なクロスワードです。より良い結果を得るために、上記の基本的なレシピにいくつかの変更を加えました。

  • クロスワードの生成の最後に、配置されたワードの数(より良い)、ボードの大きさ(より良い)、および高さと幅の比率(より近い)に基づいてスコアを付けます1に)。いくつかのクロスワードを生成し、それらのスコアを比較して、最適なクロスワードを選択します。
    • 任意の数の反復を実行する代わりに、任意の時間内にできるだけ多くのクロスワードを作成することにしました。単語リストが少ない場合は、5秒で数十のクロスワードを取得できます。より大きなクロスワードは、5〜6の可能性からのみ選択される場合があります。
  • 新しい単語を配置するときは、許容できる場所を見つけてすぐに配置するのではなく、グリッドのサイズと交差点の数を増やして、その単語の場所にスコアを付けます(理想的には、各単語を他の2〜3語が交差します)。すべての位置とそのスコアを追跡し、最適なものを選択します。

7
私は話しながらこのプログラムをたまたま書いていますが、これも私が選択したアルゴリズムと同じです。少数の単語(10以下)の場合、プログラムはすべての可能な解決策をミリ秒単位で計算しても問題はありません。ただし、アルゴリズムは指数関数的です。簡単な部分は、可能なすべての組み合わせを総当たりする基本的なアルゴリズムを書くことです。難しいのは、プログラムがすべての行き止まりのソリューションを試さないようにするために必要な数十の「ショートカット」です。
user31586 2009

2
「5. ...そして、新しい単語が干渉するかどうかを確認します」新しい単語が既存の単語の横に配置され、隣接する正方形がある場所で意味不明な表現が生成される状況をどのように説明しますか?例:レモンの消去「LE」、「ER」、「MA」などがリスト内の単語でない場合、これは誤りです。一方、そのような隣接関係を完全に拒否すると、次のような非常に優れたグリッドが
破棄される

4
@Kaffeine、うん、私はあなたが何を意味するか知っています-私はこれらのオプションを捨てなければなりませんでした。なぜなら、それらは本当に良いグリッドを作成できたとしても、チェックするのが難しすぎるためです(読む:気にすることができませんでした)。 。
nickf 2009

4
上記の推奨事項と独自のいくつかを使用してjQuery / Javascriptで構築... mlewiscs.com/crossword
MLewisCodeSolutions

@MLewisCodeSolutions素晴らしいですね。これをオープンソース化していますか?
GKS

23

最近、Pythonで独自に作成しました。ここで見つけることができます:http : //bryanhelmig.com/python-crossword-puzzle-generator/。密集したNYTスタイルのクロスワードは作成されませんが、子供のパズルブックにあるクロスワードのスタイルが作成されます。

いくつかの提案されているようなランダムなブルートフォースメソッドを使用して単語を配置するいくつかのアルゴリズムとは異なり、少しスマートなブルートフォースアプローチを単語の配置に実装しようとしました。これが私のプロセスです:

  1. 任意のサイズのグリッドと単語のリストを作成します。
  2. 単語リストを並べ替えてから、単語を最長から最短の順に並べ替えます。
  3. 最初で最も長い単語を左上端の1,1(垂直または水平)に配置します。
  4. 次の単語に移動し、単語内の各文字とグリッド内の各セルをループして、文字と文字の一致を探します。
  5. 一致が見つかったら、その位置をその単語の推奨座標リストに追加するだけです。
  6. 提案された座標リストをループして、交差する他の単語の数に基づいて単語の配置を「スコアリング」します。スコアが0の場合は、配置が悪い(既存の単語に隣接している)か、単語の交差がないことを示します。
  7. 単語リストがなくなるまで手順4に戻ります。オプションの2番目のパス。
  8. これでクロスワードが作成されましたが、ランダムな配置によって品質が低下する場合があります。そこで、このクロスワードをバッファリングして、ステップ2に戻ります。次のクロスワードのボード上に配置されているワード数が多い場合、バッファー内のクロスワードが置き換えられます。これは時間制限があります(x秒で最良のクロスワードを見つけます)。

最後に、ほぼ同じなので、適切なクロスワードパズルまたはワード検索パズルができます。うまく動作する傾向がありますが、改善について何か提案があれば教えてください。グリッドが大きくなると、指数関数的に遅くなります。大きな単語は線形にリストされます。単語リストが大きいほど、単語配置数が増える可能性が高くなります。


@ニールN:おそらく他の単語のための可能性と一致するより良い文字。おそらく、同じ結果につながる、単語ごとに含まれる個別の文字の数で並べ替える方法もあります。
Karl Adler

@NeilN Python array.sort(key=f)は安定しています。つまり、(たとえば)アルファベット順の単語リストを長さで並べ替えると、8文字の単語すべてがアルファベット順に並べ替えられます。
リン、

4
@ブライアン、あなたのウェブサイトのリンクは私にとっては機能せず、プライマリドメインはTwitterにリダイレクトするだけです。コードへの更新されたリンクはありますか?
マイケルA

2
これは(明らかに)ブライアンのジェネレーターのクローンです:github.com/jeremy886/crossword_helmig
lvictorino

20

私は実際に約10年前にクロスワード生成プログラムを書きました(それは不可解でしたが、同じルールが通常のクロスワードに適用されます)。

ファイルに格納されている単語(および関連する手がかり)のリストがあり、使用頻度の降順でソートされています(そのため、使用頻度の低い単語がファイルの先頭にあります)。テンプレートは、基本的には黒い正方形と自由な正方形を表すビットマスクであり、クライアントから提供されたプールからランダムに選択されました。

次に、パズルの完全でない単語ごとに(基本的に最初の空白の正方形を見つけて、右側(単語全体)またはその下(単語の下)も空白かどうかを確認します)の検索が行われましたその単語にすでに含まれている文字を考慮して、適合した最初の単語を探すファイル。当てはまる単語がない場合は、単語全体を不完全としてマークして先に進みます。

最後に、コンパイラーが入力する必要があるいくつかの未完成の単語があります(必要に応じて、単語と手掛かりをファイルに追加します)。彼らが思い付くことができなかった場合は任意のアイデアを、彼らは、制約を変更するか、単に総再生成のために頼むために手動でクロスワードを編集することができます。

単語/手がかりファイルが特定のサイズに達すると(このクライアントでは1日あたり50〜100の手がかりが追加されます)、クロスワードごとに2つまたは3つを超える手動修正が必要になることはほとんどありませんでした。 。


6〜12語程度のリストしかないので、これは私の状況では実際には役に立ちません。鉱山は、ワードパズルというよりは、ユーザーの学習課題に似ています。とにかく興味深いアルゴリズムの+1です!
nickf 2009年

1
素敵な説明。過去に何度か考えましたが、試したことはありません。さて、魔法の質問です。どれだけうまく機能しましたか?まばらなパズルのためだけに、または(紙のように)密なパズルのためにも?そして、密なパズルにはいくつの手がかりが必要ですか?
dmckee ---元モデレーターの子猫09/06/03

1
@dmckee、それは少し前のことですが、記憶から、密なパズルでさえかなり良かったです。多くは介入なしで完了しましたが、まだ5分の1になり、1つか2つの単語を追加する必要があります。また、ファイル内の何千もの単語について話している。間違いなくバックトラックが役に立ったかもしれませんが、余分な手がかりを考え出そうとすることを心配するよりも、クライアントが(たとえば)5つの未完成の単語で1つを拒否する方が簡単でした。5つは私が未完成の言葉のために見た外側の限界についてでした。
paxdiablo 2009年

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ボード:

(#は1つのセルに1つのチップ、%は1つのセルに2つのチップ、矢印は表示されていない)

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

生成された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)ビルダーに対するものでした。

WYSIWYGビルダーの部分は別として、コンパイラーの概要は次のとおりです。

  1. 利用可能な単語リストをロードします(単語の長さで並べ替え、つまり2,3、..、20)

  2. ユーザーが作成したグリッド上でワードスロット(つまりグリッドワード)を検索します(たとえば、長さL、水平または垂直のx、yのワード)(複雑度O(N))

  3. グリッドワードの交差点を計算します(塗りつぶす必要があります)(複雑さO(N ^ 2))

  4. 使用されているアルファベットのさまざまな文字を使用してワードリスト内の単語の共通部分を計算します(これにより、cwcで使用されているSik Cambon論文などのテンプレートを使用して一致する単語を検索できます)(複雑さO(WL * AL))

ステップ.3および.4では、このタスクを実行できます。

a。グリッドワードとそれ自体との共通部分により、このグリッドワードで使用可能な単語の関連ワードリストで一致を検索するための「テンプレート」を作成できます(特定の単語で既に入力されているこの単語と交差する他の単語の文字を使用)アルゴリズムのステップ)

b。単語リスト内の単語とアルファベットの共通部分により、特定の「テンプレート」に一致する一致(候補)単語を見つけることができます(たとえば、「A」が1位、「B」が3位など)。

したがって、これらのデータ構造を実装すると、使用されるアルゴリズムは次のようになります。

注:グリッドと単語のデータベースが一定の場合、前の手順は一度だけ実行できます。

  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. それ以外の場合、すべてのワードロットがいっぱいになると、1つの解決策があります

このアルゴリズムは、問題のソリューションツリーをランダムに一貫してウォークします。ある時点で行き止まりがある場合、前のノードに戻り、別のルートをたどります。解が見つかるか、さまざまなノードの候補の数がなくなるまで。

整合性の部分は、見つかったソリューションが実際にソリューションであることを確認し、ランダムな部分は、異なる実行で異なるソリューションを生成することを可能にし、平均してより良いパフォーマンスを持っています。

PS。これらすべて(およびその他)は、純粋なJavaScript(並列処理とWYSIWYGを使用)機能で実装されました

PS2。アルゴリズムは、複数の(異なる)ソリューションを同時に生成するために簡単に並列化できます。

お役に立てれば


1
これは、密なレイアウト(NY Timesのような)または疎なレイアウトを作成するためのものですか?
Jim

1
@ジム、これはほとんど密集したレイアウト用ですが、疎に調整することもできます。違いは、密集したレイアウト(クラシック、scandinavikなど)にあり、グリッドがあり、単語を検索しますが、フリーフォームレイアウト(スパース)の場合は、単語があり、グリッドを検索します。
Nikos M.

1
上記の手順を実装するどこかでサンプルソースを利用できますか?たとえば、私はそれのほとんどをあなたと一緒にいます(そしてすでにそれを独立し実装してます)が、「制約比率を計算する...」になると、あなたが私を失ったことを認めざるを得ません。「STH比」のようなものをウェブで検索することも、私にはあまり役に立ちません。私の実装の問題は、入力する単語を見つけようとすることは非常に非効率的で、時間がかかりすぎることです。
Jim

1
@ジム、これはすでに使用されているので持っていますが、これは特に私が持っていた仕事のために行われたもので、オープンソースプロジェクトに軽量バージョンを投稿する可能性があります。私が投稿したアルゴリズムに時間かかりすぎる場合がありますが、平均的にはかかりません)
Nikos M.

1
@ジム、このクロスワードサイト(まだ進行中)istavrolexo.gr(ギリシャ語)を見てください。さまざまな(密な)クロスワード(scandinavik、classic、sudokuなど)があり、これらは同様のアルゴリズムで生成されています(大きなscandinavikクロスワード)。
Nikos M.

9

まずは、ランダムな確率論的アプローチを使用しないでください。単語から始めて、ランダムな単語を繰り返し選択し、サイズなどの制約を破ることなく現在のパズルの状態に合わせようとします。失敗した場合は、最初からやり直してください。

このようなモンテカルロアプローチがどれほど頻繁に機能するかは、驚くでしょう。


2
はい、これは私が選択したアプローチです。非常に賢いことを試してみる必要はありません。単語を最長から最短の順に並べます。ループで、ランダムなセル(列と行の座標)を選択し、単語をボード上に配置して、単語が最後を超えていないか、または別の単語と干渉するかどうかを確認します(グリッドに単語を書き込む前に、各セルが空の場合、または文字がある場合、その文字は、書き込もうとしている文字と一致します)。境界などをチェックするためのロジックが他にもいくつかあります。私は力ずくでより小さなグリッドの束を生成し、次に交差する単語に基づいて最小ランク付けします。
Max Hodges

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();
}

wordオブジェクトスキーマがありません。wordArrayを指定してください
つまり、

文字通り、['apple'、 'orange'、 'pear']のような単語の配列
FascistDonut

こんにちは、私の編集では多くのコードは変更されませんでした。「インライン」で表示すると見た目が非常に複雑になることは承知していますが、コードの実際の変更を確認するには、「side-by-side-markdown」をクリックします。ええと...編集の説明に「フォーマットされたコード」を書くべきでしたが、まあ。
2

これはどのように作動しますか?このJavaScriptを組み込んだHTMLファイルを提供できますか?
GKS

5

長さとスクラブルスコアの2つの数値を生成します。スクラブルスコアが低いと、参加しやすくなります(スコアが低い=一般的な文字が多い)。リストを長さの降順とスクラブルスコアの昇順で並べ替えます。

次に、リストを下に移動します。単語が既存の単語と交差しない場合(それぞれの長さとスクラブルスコアでそれぞれの単語をチェックします)、キューに入れ、次の単語をチェックします。

すすぎ、繰り返します。これにより、クロスワードが生成されます。

もちろん、これはO(n!)であると確信しています。クロスワードの完成が保証されているわけではありませんが、誰かがそれを改善できるかもしれません。


3

私はこの問題について考えてきました。私の感覚では、本当に密なクロスワードを作成するには、限られた単語リストで十分であると期待することはできません。したがって、ディクショナリを取得して「トライ」データ構造に配置することができます。これにより、残りのスペースを埋める単語を簡単に見つけることができます。トライでは、たとえば「c?t」という形式のすべての単語を提供するトラバーサルを実装するのがかなり効率的です。

したがって、私の一般的な考え方は、低密度のクロスを作成するためにここで説明されているようなある種の比較的ブルートフォースアプローチを作成し、空白を辞書の単語で埋めます。

他の誰かがこのアプローチを取っている場合は、私に知らせてください。


3

私はクロスワードジェネレーターエンジンをいじっていましたが、これが最も重要であることがわかりました。

0。!/usr/bin/python

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

    b。後でランダムに選択してイテレートしたくない場合を除き、簡単な方向付けのために行列を歩き回るカーソルのようなアイテム/オブジェクトを作成します。

  2. 最初に、最初のペアをピックアップし、それらを0,0の前後に配置します。最初のものを現在のクロスワード「リーダー」として保存します。

  3. 次の空のセルに、対角線またはランダムな確率でカーソルを移動します。

  4. のような単語を反復処理し、空き領域の長さを使用して最大単語長を定義します。 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

@Jon Glazer:通常、クロスワードキーを関数自体に送信しますが、クロスワードをの直後の文字の2D配列として記録できますvar crosswords = generateCrosswordBlockSources(puzzlewords);。この値をログに記録するだけです。ゲームには「チートモード」があり、「Reveal Answer」をクリックするだけですぐに値を取得できることを忘れないでください。
HoldOffHunger

これにより、隣接する「ダウン」ボックスのある場所に、意味不明な「アクロス」単語を含むパズルが生成されます。逆も同様です。それはかかわらず、標準的なクロスワードパズルは、このように動作していない密度を最大化します。
Beejor

1

これはハーバード大学のAI CS50コースのプロジェクトとして表示されます。アイデアは、クロスワード生成問題を制約充足問題として定式化し、異なるヒューリスティックを使用したバックトラックで解決して、検索スペースを削減することです。

まず、いくつかの入力ファイルが必要です。

  1. クロスワードパズルの構造(次のようになります。たとえば、「#」は埋められない文字を表し、「_」は埋められる文字を表します)

`

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

`

  1. 候補となる単語が選択される入力語彙(単語リスト/辞書)(以下に示すような)。

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

これで、CSPが定義され、次のように解決されます。

  1. 変数は、入力として提供される単語(語彙)のリストからの値(つまり、そのドメイン)を持つように定義されます。
  2. 各変数は3つのタプルで表されます:(grid_coordinate、direction、length)ここで、座標は対応する単語の開始を表し、方向は水平または垂直のいずれかであり、長さは変数が持つ単語の長さとして定義されますに割り当てられた。
  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は非常に興味深い説明です。しかし、ここで解決しようとしていた問題のコンテキストは、すべてを使用する必要がある小さな単語のセットがあり、レイアウトから始めて単語を見つけるのではなく、クロスワードに最適なレイアウトを見つけようとしていたことでした。合う。
nickf
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.