Algorithme pour générer un mot croisé


123

Étant donné une liste de mots, comment procéderiez-vous pour les organiser dans une grille de mots croisés?

Cela n'aurait pas à être comme un «vrai» mot croisé symétrique ou quelque chose comme ça: il suffit de sortir une position de départ et une direction pour chaque mot.

Réponses:


62

J'ai trouvé une solution qui n'est probablement pas la plus efficace, mais qui fonctionne assez bien. Fondamentalement:

  1. Triez tous les mots par longueur, par ordre décroissant.
  2. Prenez le premier mot et placez-le sur le tableau.
  3. Prenez le mot suivant.
  4. Recherchez parmi tous les mots qui sont déjà sur le tableau et voyez s'il y a des intersections possibles (des lettres courantes) avec ce mot.
  5. S'il y a un emplacement possible pour ce mot, parcourez tous les mots qui se trouvent sur le tableau et vérifiez si le nouveau mot interfère.
  6. Si ce mot ne brise pas le tableau, placez-le là et passez à l'étape 3, sinon continuez à chercher un lieu (étape 4).
  7. Continuez cette boucle jusqu'à ce que tous les mots soient placés ou ne puissent pas être placés.

Cela fait des mots croisés fonctionnels, mais souvent assez médiocres. J'ai apporté un certain nombre de modifications à la recette de base ci-dessus pour obtenir un meilleur résultat.

  • À la fin de la génération d'un mot croisé, attribuez-lui un score basé sur le nombre de mots placés (plus il y en a, mieux c'est), la taille du tableau (le plus petit sera le mieux) et le rapport entre la hauteur et la largeur (le plus proche à 1 le mieux). Générez un certain nombre de mots croisés, puis comparez leurs scores et choisissez le meilleur.
    • Au lieu d'exécuter un nombre arbitraire d'itérations, j'ai décidé de créer autant de mots croisés que possible dans un laps de temps arbitraire. Si vous n'avez qu'une petite liste de mots, vous obtiendrez des dizaines de mots croisés possibles en 5 secondes. Un mot croisé plus grand ne peut être choisi que parmi 5 à 6 possibilités.
  • Lorsque vous placez un nouveau mot, au lieu de le placer immédiatement après avoir trouvé un emplacement acceptable, attribuez à ce mot un score en fonction de l'augmentation de la taille de la grille et du nombre d'intersections (idéalement, vous voudriez que chaque mot soit traversé par 2-3 autres mots). Gardez une trace de toutes les positions et de leurs scores, puis choisissez la meilleure.

7
Il se trouve que j'écris ce programme au moment où nous parlons, et c'est l'algorithme identique que j'ai choisi aussi. Pour un petit nombre de mots (10 ou moins), le programme n'a aucun mal à calculer toutes les solutions possibles en millisecondes. L'algorithme est cependant exponentiel. La partie la plus simple consiste à écrire l'algorithme de base qui force brutalement toutes les combinaisons possibles. Le plus difficile est la douzaine de «raccourcis» dont vous avez besoin pour empêcher le programme d'essayer toutes les solutions sans issue.
user31586

2
"5. ... et vérifiez si le nouveau mot interfère" Comment expliquez-vous les situations où le nouveau mot est placé à côté d'un mot existant, ce qui génère du charabia aux endroits où il a des carrés adjacents? Ex: LEMON ERASE Si «LE», «ER» et «MA» etc. ne sont pas des mots dans votre liste, c'est faux. D'un autre côté, rejeter catégoriquement de telles adjacences pourrait jeter de très bonnes grilles, comme: W LEMON ERASE NEXUS TT
George Armhold

4
@Kaffeine, oui je sais ce que tu veux dire - j'ai dû jeter ces options parce que même si elles pouvaient créer de très bonnes grilles, c'est trop difficile à vérifier (lire: je ne pouvais pas être dérangé) , et il y a de fortes chances que ce soit juste une interférence de toute façon .
nickf le

4
Construit avec jQuery / Javascript en utilisant les recommandations ci-dessus et quelques-unes de mes propres ... mlewiscs.com/crossword
MLewisCodeSolutions

@MLewisCodeSolutions a l'air génial. Êtes-vous en open source?
GKS

23

J'ai récemment écrit le mien en Python. Vous pouvez le trouver ici: http://bryanhelmig.com/python-crossword-puzzle-generator/ . Cela ne crée pas les mots croisés de style NYT dense, mais le style de mots croisés que vous pourriez trouver dans le livre de puzzle d'un enfant.

Contrairement à quelques algorithmes que j'ai découverts et qui implémentaient une méthode par force brute aléatoire pour placer des mots comme certains l'ont suggéré, j'ai essayé de mettre en œuvre une approche de force brute légèrement plus intelligente lors du placement des mots. Voici mon processus:

  1. Créez une grille de n'importe quelle taille et une liste de mots.
  2. Mélangez la liste de mots, puis triez les mots du plus long au plus court.
  3. Placez le premier mot et le plus long à la position la plus à gauche en haut, 1,1 (vertical ou horizontal).
  4. Passez au mot suivant, parcourez chaque lettre du mot et chaque cellule de la grille à la recherche de correspondance lettre à lettre.
  5. Lorsqu'une correspondance est trouvée, ajoutez simplement cette position à une liste de coordonnées suggérée pour ce mot.
  6. Faites une boucle sur la liste de coordonnées suggérée et «notez» le placement des mots en fonction du nombre d'autres mots croisés. Les scores de 0 indiquent soit un mauvais placement (à côté des mots existants), soit l'absence de croisements de mots.
  7. Revenez à l'étape 4 jusqu'à ce que la liste de mots soit épuisée. Deuxième passe en option.
  8. Nous devrions maintenant avoir un mot croisé, mais la qualité peut être frappée ou ratée en raison de certains emplacements aléatoires. Donc, nous tamponnons ce mot croisé et revenons à l'étape 2. Si le mot croisé suivant a plus de mots placés sur le tableau, il remplace les mots croisés dans la mémoire tampon. Ceci est limité dans le temps (trouvez les meilleurs mots croisés en x secondes).

À la fin, vous avez un puzzle de mots croisés ou de recherche de mots décent, car ils sont à peu près les mêmes. Il a tendance à fonctionner plutôt bien, mais faites-moi savoir si vous avez des suggestions d'amélioration. Les réseaux plus grands fonctionnent exponentiellement plus lentement; de plus grandes listes de mots linéairement. Les listes de mots plus volumineuses ont également beaucoup plus de chances d'obtenir de meilleurs numéros de placement de mots.


@Neil N: Probablement une meilleure possibilité de correspondance de lettres pour les autres mots. Serait peut-être aussi une approche pour trier par le nombre de lettres distinctes contenues par mot, ce qui conduira principalement au même résultat.
Karl Adler

@NeilN Python array.sort(key=f)est stable, ce qui signifie (par exemple) que le simple tri d'une liste de mots alphabétique par longueur conserverait tous les mots de 8 lettres classés par ordre alphabétique.
Lynn le

4
@Bryan, le lien de votre site Web ne fonctionne pas pour moi et le domaine principal redirige simplement vers Twitter. Avez-vous un lien mis à jour vers votre code?
Michael A

2
Voici (apparemment) un clone du générateur de Bryan: github.com/jeremy886/crossword_helmig
lvictorino

20

En fait, j'ai écrit un programme de génération de mots croisés il y a environ dix ans (c'était cryptique mais les mêmes règles s'appliqueraient pour les mots croisés normaux).

Il contenait une liste de mots (et des indices associés) stockés dans un fichier trié par utilisation décroissante à ce jour (de sorte que les mots les moins utilisés étaient en haut du fichier). Un modèle, essentiellement un masque de bits représentant les carrés noirs et libres, a été choisi au hasard dans un pool fourni par le client.

Ensuite, pour chaque mot non complet du puzzle (recherchez essentiellement le premier carré vide et voyez si celui à droite (à travers le mot) ou celui en dessous (mot en bas) est également vide), une recherche a été effectuée sur le fichier recherchant le premier mot qui correspondait, en tenant compte des lettres déjà présentes dans ce mot. S'il n'y avait pas de mot qui puisse convenir, vous venez de marquer le mot entier comme incomplet et de passer à autre chose.

À la fin, il y aurait des mots incomplets que le compilateur devrait remplir (et ajouter le mot et un indice au fichier si désiré). S'ils ne pouvaient pas venir avec des idées, ils pourraient modifier les mots croisés pour modifier les contraintes ou tout simplement demander manuellement une nouvelle génération totale.

Une fois que le fichier de mots / indices a atteint une certaine taille (et qu'il ajoutait 50 à 100 indices par jour pour ce client), il y avait rarement un cas de plus de deux ou trois corrections manuelles qui devaient être faites pour chaque mot croisé .


Cela ne m'aide pas vraiment dans ma situation, car je n'ai qu'une liste d'environ 6 à 12 mots. Le mien ressemble plus à un exercice d'apprentissage pour l'utilisateur qu'à un puzzle de mots. +1 pour l'algorithme intéressant quand même!
nickf

1
Belle description. J'y ai pensé plusieurs fois dans le passé, mais je ne l'ai jamais essayé. Maintenant, pour la question magique: comment cela a-t-il fonctionné? Juste pour les puzzles clairsemés, ou aussi pour les puzzles denses (comme dans le papier)? Et de combien d'indices avez-vous besoin pour des énigmes denses?
dmckee --- ex-moderator chaton

1
@dmckee, c'était il y a quelque temps mais de mémoire, même les énigmes denses étaient plutôt bonnes. Beaucoup ont été complétés sans intervention, mais vous en obtiendriez peut-être un cinquième nécessitant l'ajout d'un ou deux mots supplémentaires. Et nous parlons de milliers de mots dans le fichier. Il ne fait aucun doute que le retour en arrière aurait pu aider, mais il était plus facile pour le client d'en rejeter un avec (par exemple) 5 mots inachevés que de s'inquiéter d'essayer de trouver des indices supplémentaires. Cinq était à peu près la limite extérieure que je voyais pour les mots inachevés.
paxdiablo

16

Cet algorithme crée 50 mots croisés de flèches 6x9 denses en 60 secondes. Il utilise une base de données de mots (avec mot + astuces) et une base de données de tableaux (avec des tableaux préconfigurés).

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

Une plus grande base de données de mots réduit considérablement le temps de génération et certains types de tableaux sont plus difficiles à remplir! Les planches plus grandes nécessitent plus de temps pour être remplies correctement!


Exemple:

Carte 6x9 pré-configurée:

(# signifie un conseil dans une cellule,% signifie deux conseils dans une cellule, flèches non affichées)

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

Carte 6x9 générée:

# 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 

Conseils [ligne, colonne]:

[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

Bien que ce soit une question plus ancienne, j'essaierai une réponse basée sur un travail similaire que j'ai effectué.

Il existe de nombreuses approches pour résoudre les problèmes de contraintes (qui appartiennent généralement à la classe de complexité NPC).

Ceci est lié à l'optimisation combinatoire et à la programmation par contraintes. Dans ce cas, les contraintes sont la géométrie du maillage et l'exigence que les mots soient uniques etc.

Les approches de randomisation / recuit peuvent également fonctionner (bien que dans le cadre approprié).

Une simplicité efficace pourrait bien être la sagesse ultime!

Les exigences étaient pour un compilateur de mots croisés plus ou moins complet et un constructeur (visuel WYSIWYG).

Laissant de côté la partie du constructeur WYSIWYG, le plan du compilateur était le suivant:

  1. Charger les listes de mots disponibles (triées par longueur de mot, c'est-à-dire 2,3, .., 20)

  2. Trouvez les espaces de mots (c'est-à-dire les mots de la grille) sur la grille construite par l'utilisateur (par exemple, mot à x, y de longueur L, horizontal ou vertical) (complexité O (N))

  3. Calculer les points d'intersection des mots de la grille (qui doivent être remplis) (complexité O (N ^ 2))

  4. Calculer les intersections des mots dans les listes de mots avec les différentes lettres de l'alphabet utilisé (cela permet de rechercher des mots correspondants en utilisant un modèle par exemple. Thèse Sik Cambon telle qu'utilisée par cwc ) (complexité O (WL * AL))

Les étapes .3 et .4 permettent d'effectuer cette tâche:

une. Les intersections des mots de la grille avec eux-mêmes permettent de créer un "modèle" pour essayer de trouver des correspondances dans la liste de mots associée des mots disponibles pour ce mot de la grille (en utilisant les lettres d'autres mots croisés avec ce mot qui sont déjà remplis à un certain étape de l'algorithme)

b. Les intersections des mots d'une liste de mots avec l'alphabet permettent de trouver des mots (candidats) correspondants qui correspondent à un "modèle" donné (par exemple 'A' en 1ère place et 'B' en 3ème place etc.)

Donc, avec ces structures de données implémentées, l'algorithme utilisé était comme ceci:

REMARQUE: si la grille et la base de données de mots sont constantes, les étapes précédentes ne peuvent être effectuées qu'une seule fois.

  1. La première étape de l'algorithme est de sélectionner un emplacement de mots vide (mot de la grille) au hasard et de le remplir avec un mot candidat de sa liste de mots associée (la randomisation permet de produire différents solutons lors d'exécutions consécutives de l'algorithme) (complexité O (1) ou O ( N))

  2. Pour chaque créneau de mots encore vide (qui ont des intersections avec des lots de mots déjà remplis), calculez un rapport de contrainte (cela peut varier, sth simple est le nombre de solutions disponibles à cette étape) et triez les lots de mots vides par ce rapport (complexité O (NlogN ) ou O (N))

  3. Faites une boucle dans les espaces de mots vides calculés à l'étape précédente et essayez pour chacun d'entre eux un certain nombre de solutions annulées (en vous assurant que la "cohérence d'arc est conservée", c'est-à-dire que la grille a une solution après cette étape si ce mot est utilisé) et triez-les selon disponibilité maximale pour l'étape suivante (c'est-à-dire que l'étape suivante a un maximum de solutions possibles si ce mot est utilisé à ce moment-là à cet endroit, etc.) (complexité O (N * MaxCandidatesUsed))

  4. Remplissez ce mot (marquez-le comme rempli et passez à l'étape 2)

  5. Si aucun mot ne satisfait aux critères de l'étape .3, essayez de revenir en arrière vers une autre solution candidate d'une étape précédente (les critères peuvent varier ici) (complexité O (N))

  6. Si un retour en arrière est trouvé, utilisez l'alternative et réinitialisez éventuellement tous les mots déjà remplis qui pourraient nécessiter une réinitialisation (marquez-les à nouveau comme non remplis) (complexité O (N))

  7. Si aucun retour en arrière n'est trouvé, aucune solution ne peut être trouvée (au moins avec cette configuration, graine initiale etc.)

  8. Sinon, lorsque tous les lots de mots sont remplis, vous avez une solution

Cet algorithme effectue une marche cohérente aléatoire de l'arbre de solution du problème. Si à un moment donné il y a une impasse, il fait un retour en arrière vers un nœud précédent et suit une autre route. Jusqu'à ce qu'une solution trouvée ou le nombre de candidats pour les différents nœuds soient épuisés.

La partie cohérence s'assure qu'une solution trouvée est bien une solution et la partie aléatoire permet de produire différentes solutions dans différentes exécutions et aussi en moyenne d'avoir de meilleures performances.

PS. tout cela (et d'autres) ont été implémentés en JavaScript pur (avec traitement parallèle et WYSIWYG)

PS2. L'algorithme peut être facilement parallélisé afin de produire plus d'une solution (différente) en même temps

J'espère que cela t'aides


1
Est-ce pour créer des mises en page denses (comme le NY Times) ou des mises en page clairsemées?
Jim

1
@Jim, c'est principalement pour les mises en page denses, mais peut également être ajusté pour les mises en page éparses. La différence est dans les mises en page denses (par exemple classique, scandinavik etc.) on a la grille et recherche des mots, alors que pour les mises en page de forme libre (clairsemée) on a les mots et recherche une grille.
Nikos M.

1
Auriez-vous un exemple de source disponible quelque part qui implémente les étapes ci-dessus. Par exemple, je suis avec vous pour la plupart (et j'ai déjà implémenté la plupart indépendamment), mais quand il s'agit de "calculer un rapport de contraintes ...", je dois admettre que vous m'avez perdu. Faire des recherches sur le Web pour des choses comme "STH Ratio" ne m'aide pas non plus. Le problème avec ma mise en œuvre est d'essayer de trouver les mots à remplir est très inefficace et prend beaucoup trop de temps.
Jim

1
@Jim, j'ai car cela est déjà utilisé, mais cela a été fait spécifiquement pour un travail que j'avais, il est possible que je publie une version légère sur mes projets open source, si vous avez besoin de plus d'aide contactez-moi (ps en effet à certains cas, l'algorithme que j'ai publié peut prendre trop de temps, mais en moyenne, ce n'est pas le cas)
Nikos M.

1
@Jim, jetez un œil à ce site de mots croisés (toujours en cours) istavrolexo.gr (en grec) avec divers mots croisés (denses) (ie scandinavik, classique, sudoku) qui ont été générés par un algorithme similaire ( un grand mot croisé scandinavik )
Nikos M.

9

Pourquoi ne pas simplement utiliser une approche probabiliste aléatoire pour commencer. Commencez par un mot, puis choisissez à plusieurs reprises un mot au hasard et essayez de l'adapter à l'état actuel du puzzle sans casser les contraintes de taille etc. Si vous échouez, recommencez simplement.

Vous serez surpris de la fréquence à laquelle une approche de Monte Carlo comme celle-ci fonctionne.


2
oui, c'est l'approche que j'ai choisie. Vous n'êtes pas obligé d'essayer d'être très intelligent. Ordonnez les mots du plus long au plus court. Dans une boucle, choisissez une cellule aléatoire (coordonnées de colonne et de ligne) et placez le mot sur le tableau de test pour voir s'il se termine ou s'il interfère avec un autre mot (avant d'écrire le mot sur la grille, vérifiez que chaque cellule est soit vide ou s'il y a une lettre, cette lettre correspond à la lettre que vous essayez d'écrire). Il y a une autre logique pour vérifier les limites et tout. La force brute génère un tas de grilles de plus en plus petites, puis classe les plus petites en fonction des mots qui se croisent.
Max Hodges

6

Voici un code JavaScript basé sur la réponse de nickf et le code Python de Bryan. Il suffit de le publier au cas où quelqu'un d'autre en aurait besoin dans 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();
}

Le schéma d'objet Word est manquant, veuillez fournir wordArray
ie

Littéralement juste un tableau de mots comme ['pomme', 'orange', 'poire']
FascistDonut

Salut, pour info, ma modification n'a pas changé beaucoup de code, elle l'a juste formatée. Je sais que cela semble si désordonné lors de la visualisation «en ligne», mais si vous voulez voir les changements réels dans le code, cliquez sur «côte à côte-markdown». Eh bien ... j'aurais dû écrire "Code formaté" dans la description d'édition, mais bon.
double bip le

Comment cela marche-t-il? Pouvez-vous fournir un fichier html incorporant ce javascript?
GKS

5

Je générerais deux nombres: la longueur et le score au Scrabble. Supposons qu'un faible score au Scrabble signifie qu'il est plus facile de rejoindre (scores faibles = beaucoup de lettres courantes). Triez la liste par longueur décroissante et par score Scrabble croissant.

Ensuite, descendez simplement la liste. Si le mot ne croise pas avec un mot existant (vérifiez chaque mot par sa longueur et son score au Scrabble, respectivement), mettez-le dans la file d'attente et vérifiez le mot suivant.

Rincez et répétez, et cela devrait générer un mot croisé.

Bien sûr, je suis à peu près sûr que c'est O (n!) Et il n'est pas garanti de compléter les mots croisés pour vous, mais peut-être que quelqu'un peut l'améliorer.


3

J'ai réfléchi à ce problème. Mon sentiment est que pour créer un mot croisé vraiment dense, vous ne pouvez pas espérer que votre liste de mots limitée suffira. Par conséquent, vous voudrez peut-être prendre un dictionnaire et le placer dans une structure de données "trie". Cela vous permettra de trouver facilement des mots qui remplissent les espaces à gauche. Dans un essai, il est assez efficace d'implémenter un parcours qui, par exemple, vous donne tous les mots de la forme "c? T".

Donc, ma pensée générale est la suivante: créer une sorte d'approche de force relativement brute comme certains l'ont décrit ici pour créer une croix de faible densité, et remplir les espaces avec des mots du dictionnaire.

Si quelqu'un d'autre a adopté cette approche, veuillez me le faire savoir.


3

Je jouais avec le moteur de générateur de mots croisés, et j'ai trouvé que c'était le plus important:

0.!/usr/bin/python

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

    b. Créez un élément / objet comme un curseur qui se déplacera autour de la matrice pour une orientation facile à moins que vous ne vouliez itérer par choix aléatoire plus tard.

  2. la première, prenez la première paire et placez-les en travers et en bas de 0,0; stockez le premier comme notre «leader» actuel de mots croisés.

  3. déplacer le curseur par ordre diagonal ou aléatoire avec une plus grande probabilité diagonale vers la prochaine cellule vide

  4. itérer sur les mots comme et utiliser la longueur de l'espace libre pour définir la longueur maximale des mots: 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. pour comparer le mot à l'espace libre, j'ai utilisé, c'est-à-dire:

    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. après chaque mot utilisé avec succès, changez de direction. Bouclez alors que toutes les cellules sont remplies OU vous manquez de mots OU par limite d'itérations alors:

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

... et répétez de nouveaux mots croisés.

  1. Faites le système de notation par facilité de remplissage et quelques calculs d'estimation. Donnez le score pour le mot croisé actuel et restreignez le choix ultérieur en l'ajoutant à la liste des mots croisés créés si le score est satisfait par votre système de notation.

  2. Après la première session d'itération, parcourez à nouveau la liste des mots croisés créés pour terminer le travail.

En utilisant plus de paramètres, la vitesse peut être améliorée par un facteur énorme.


2

J'obtiendrais un index de chaque lettre utilisée par chaque mot pour connaître les croix possibles. Ensuite, je choisirais le mot le plus grand et l'utiliserais comme base. Sélectionnez le prochain grand et traversez-le. Rincez et répétez. C'est probablement un problème NP.

Une autre idée consiste à créer un algorithme génétique où la métrique de force est le nombre de mots que vous pouvez mettre dans la grille.

La partie difficile que je trouve est de savoir quand connaître une certaine liste ne peut pas être franchie.


1
Je pensais aussi à un algorithme génétique. La fonction de remise en forme pourrait être à quel point les mots sont insérés dans la grille.
Adrian McCarthy

2

Celui-ci apparaît comme un projet dans le cours AI CS50 de Harvard. L'idée est de formuler le problème de génération de mots croisés comme un problème de satisfaction de contraintes et de le résoudre par retour arrière avec différentes heuristiques pour réduire l'espace de recherche.

Pour commencer, nous avons besoin de quelques fichiers d'entrée:

  1. La structure du jeu de mots croisés (qui ressemble à la suivante, par exemple, où le '#' représente les caractères à ne pas remplir et '_' représente les caractères à remplir)

»

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

»

  1. Un vocabulaire d'entrée (liste de mots / dictionnaire) à partir duquel les mots candidats seront choisis (comme celui illustré ci-dessous).

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

Maintenant, le CSP est défini et doit être résolu comme suit:

  1. Les variables sont définies pour avoir des valeurs (c'est-à-dire leurs domaines) de la liste de mots (vocabulaire) fournie en entrée.
  2. Chaque variable est représentée par un 3 tuple: (grid_coordinate, direction, length) où la coordonnée représente le début du mot correspondant, la direction peut être horizontale ou verticale et la longueur est définie comme la longueur du mot la variable sera assigné à.
  3. Les contraintes sont définies par l'entrée de structure fournie: par exemple, si une variable horizontale et une variable verticale ont un caractère commun, elles seront représentées comme une contrainte de chevauchement (arc).
  4. Désormais, les algorithmes de cohérence de nœud et de cohérence d'arc AC3 peuvent être utilisés pour réduire les domaines.
  5. Ensuite, revenir en arrière pour obtenir une solution (s'il en existe une) au CSP avec MRV (valeur minimale restante), degré etc. commande, pour accélérer l'algorithme de recherche.

Ce qui suit montre la sortie obtenue à l'aide d'une implémentation de l'algorithme de résolution 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█

»

L'animation suivante montre les étapes de retour en arrière:

entrez la description de l'image ici

En voici un autre avec une liste de mots en langue Bangla (bengali):

entrez la description de l'image ici


+1 pour une explication vraiment intéressante. Cependant, le contexte du problème que j'essayais de résoudre ici était qu'il y avait un petit ensemble de mots qui devaient tous être utilisés, et j'essayais de trouver une mise en page optimale pour les mots croisés, plutôt que de commencer par une mise en page et de trouver des mots qui correspondent.
nickf

1

Générateur et jeu de mots croisés jQuery

J'ai codé une solution JavaScript / jQuery à ce problème:

Exemple de démonstration: http://www.earthfluent.com/crossword-puzzle-demo.html

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

L'intention de l'algorithme que j'ai utilisé:

  1. Réduisez autant que possible le nombre de carrés inutilisables dans la grille.
  2. Ayez autant de mots mélangés que possible.
  3. Calculez en un temps extrêmement rapide.

Démonstration d'un jeu de mots croisés généré.

Je décrirai l'algorithme que j'ai utilisé:

  1. Regroupez les mots selon ceux qui partagent une lettre commune.

  2. À partir de ces groupes, créez des ensembles d'une nouvelle structure de données ("blocs de mots"), qui est un mot principal (qui traverse tous les autres mots), puis les autres mots (qui traversent le mot principal).

  3. Commencez le puzzle de mots croisés avec le tout premier de ces blocs de mots dans la position tout en haut à gauche du puzzle de mots croisés.

  4. Pour le reste des blocs de mots, en partant de la position la plus en bas à droite du mot croisé, déplacez-vous vers le haut et vers la gauche, jusqu'à ce qu'il n'y ait plus d'emplacements disponibles à remplir. S'il y a plus de colonnes vides vers le haut que vers la gauche, déplacez-vous vers le haut et vice versa.


@holdoffhunger Avez-vous une méthode pour afficher la clé des mots croisés? Des boîtes avec des lettres remplies?
Jon Glazer

@Jon Glazer: En règle générale, vous envoyez les clés de mots croisés à la fonction elle-même, mais vous pouvez enregistrer les mots croisés sous forme de tableau 2D de caractères, juste après var crosswords = generateCrosswordBlockSources(puzzlewords);. Consignez simplement cette valeur sur la console. N'oubliez pas, il y a un "cheat-mode" dans le jeu, où vous pouvez simplement cliquer sur "Révéler la réponse", pour obtenir la valeur immédiatement.
HoldOffHunger

Cela génère des énigmes avec des mots charabiaques "à travers" dans des endroits avec des cases "en bas" adjacentes, et vice versa. Puzzles de mots croisés standard faites cela, si ça marche pas comme le fait de maximiser la densité.
Beejor le
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.