Avec une liste de mots, comment voudriez-vous les organiser dans une grille de mots croisés?
Cela ne devrait pas être comme un "bon" jeu de mots croisés qui soit symétrique ou quelque chose du genre: en gros, il suffit de donner une position de départ et une direction pour chaque mot.
Y aurait-il des exemples Java disponibles?
J'ai proposé une solution qui n'est probablement pas la plus efficace, mais elle fonctionne assez bien. Fondamentalement:
Cela fait des mots croisés de travail, mais souvent assez pauvres. J'ai apporté plusieurs modifications à la recette de base ci-dessus pour obtenir un meilleur résultat.
J'ai récemment écrit le mien en Python. Vous pouvez le trouver ici: http://bryanhelmig.com/python-crossword-puzzle-generator/ . Il ne crée pas les mots croisés denses de style NYT, 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 trouvés là-bas qui implémentaient une méthode aléatoire de placement de mots comme certains l'ont suggéré, j'ai essayé d'implémenter une approche légèrement plus intelligente de force brute lors du placement de Word. Voici mon processus:
À la fin, vous avez des mots croisés ou des mots cachés, car ils sont à peu près les mêmes. Cela fonctionne plutôt bien, mais laissez-moi savoir si vous avez des suggestions d'amélioration. Les plus grandes grilles fonctionnent de manière exponentielle plus lente; les plus grandes listes de mots linéairement. De plus grandes listes de mots ont également beaucoup plus de chances d'obtenir de meilleurs numéros de placement de mots.
En fait, j’ai écrit un programme de génération de mots croisés il ya environ dix ans (c’était cryptique, mais les mêmes règles s’appliqueraient aux mots croisés normaux).
Il contenait une liste de mots (et d’indices associés) stockés dans un fichier, triés par ordre décroissant d’utilisation jusqu’à présent (de sorte que les mots moins utilisés se trouvaient 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 (trouvez le premier carré vide et voyez si celui qui se trouve à droite (mot croisé) ou celui du dessous (mot caché) est également vide), une recherche a été effectuée: le fichier à la recherche du premier mot qui convient, en tenant compte des lettres déjà présentes dans ce mot. S'il n'y avait pas de mot qui puisse tenir, vous venez de marquer le mot comme incomplet et de passer à autre chose.
À la fin, il y aurait quelques mots incomplets que le compilateur devrait remplir (et ajouter le mot et un indice au fichier si vous le souhaitez). S'ils ne pouvaient pas trouver d'idées aucune, ils pourraient modifier le mot croisé manuellement pour modifier les contraintes ou simplement demander une régénération totale.
Une fois que le fichier Word/indice a atteint une certaine taille (et qu'il ajoutait 50 à 100 indices par jour pour ce client), il était rarement nécessaire de corriger plus de deux ou trois corrections manuelles pour chaque mot croisé. .
Cet algorithme crée 50 denses 6x9 mots croisés sur les flèches en 60 secondes. Il utilise une base de données Word (avec Word + astuces) et une base de données de cartes (avec des cartes préconfigurées).
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 Word réduit considérablement le temps de génération et certains types de tableaux sont plus difficiles à remplir! Les plus gros tableaux demandent plus de temps pour être remplis correctement!
Exemple:
Carte 6x9 pré-configurée:
(# signifie un bout dans une cellule,% signifie deux bouts dans une cellule, flèches non représentées)
# - # # - % # - #
- - - - - - - - -
# - - - - - # - -
% - - # - # - - -
% - - - - - % - -
- - - - - - - - -
Tableau 6x9 généré:
# 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
Astuces [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)
Pourquoi ne pas simplement utiliser une approche probabiliste aléatoire pour commencer. Commencez avec un mot, puis choisissez de manière répétée un mot aléatoire et essayez de l'adapter à l'état actuel du puzzle sans rompre les contraintes de taille, etc. Si vous échouez, recommencez à nouveau.
Vous serez surpris de la fréquence à laquelle une approche de Monte Carlo comme celle-ci fonctionne.
Bien qu’il s’agisse d’une question plus ancienne, je tenterai de répondre à un travail similaire que j’ai effectué.
Il existe de nombreuses approches pour résoudre les problèmes de contraintes (qui sont généralement dans NPC)).
Ceci est lié à l'optimisation combinatoire et à la programmation par contraintes. Dans ce cas, les contraintes sont la géométrie de la grille 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 concernaient un compilateur de mots croisés plus ou moins complet et un constructeur (visuel WYSIWYG).
Laissant de côté la partie constructeur WYSIWYG, le contour du compilateur était le suivant:
Charger les listes de mots disponibles (triés par longueur de mot, soit 2,3, .., 20)
Recherchez les emplacements de mots (c.-à-d. Mots de grille) sur la grille construite par l'utilisateur (par exemple, mot à x, y de longueur L, horizontal ou vertical) (complexité O(N))
Calcule les points d'intersection des mots de la grille (à renseigner) (complexité O (N ^ 2))
Calculez 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 à l'aide d'un modèle, par exemple. thèse de 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 pour les mots disponibles pour ce mot de grille (en utilisant les lettres des autres mots qui se croisent avec ce mot qui sont déjà remplies à un certain moment). pas de l'algorithme)
b. Les intersections des mots d'une liste de mots avec l'alphabet permettent de trouver des mots correspondants (candidats) correspondant à un "modèle" donné (par exemple, "A" à la 1ère place et "B" à la 3ème place, etc.).
Donc, avec ces structures de données implémentées, l'algorithme utilisé était qc 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 fois.
La première étape de l’algorithme consiste à sélectionner au hasard un mot vide (grille de mots) vide et à le remplir avec un mot candidat de la liste de mots correspondante (la randomisation permet de produire différentes solutions lors de l’exécution consécutive de l’algorithme) (complexité O(1) ou O(N))
Pour chaque emplacement de mot encore vide (ayant des intersections avec des mots déjà remplis), calculez un rapport de contrainte (cela peut varier, sth est simple le nombre de solutions disponibles à ce stade) et triez les mots vides par ce rapport (complexité O(NlogN) ou O(N)))
Parcourez les mots vides calculés à l'étape précédente et essayez pour chacun d'entre eux un certain nombre de solutions d'annulation (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 en fonction de disponibilité maximale pour l'étape suivante (c.-à-d. que l'étape suivante comporte un nombre maximal de solutions possibles si ce mot est utilisé à ce moment-là, etc.) (complexité O (N * MaxCandidatesUsed))
Remplissez cette parole (marquez-la comme remplie et passez à l'étape 2)
Si aucun mot trouvé ne satisfait aux critères de l'étape .3, essayez de revenir à une autre solution candidate d'une étape précédente (les critères peuvent varier ici) (complexité O(N))).
Si vous trouvez un retour arrière, utilisez l’alternative et réinitialisez éventuellement les mots déjà renseignés qui pourraient nécessiter une réinitialisation (marquez-les comme remplis à nouveau) (complexité O(N))
Si aucun retour en arrière n'a été trouvé, aucune solution ne peut être trouvée (au moins avec cette configuration, graine initiale, etc.)
Sinon, lorsque tous les mots sont remplis, vous avez une solution.
Cet algorithme effectue une marche cohérente aléatoire de l'arborescence de solutions du problème. Si à un moment donné il y a une impasse, il retourne à un nœud précédent et suit un autre chemin. Jusqu'à ce qu'une solution soit trouvée ou que le nombre de candidats pour les différents nœuds ne soit épuisé.
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, en moyenne, d'obtenir 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 mis en parallèle afin de produire plus d'une solution (différente) à la fois.
J'espère que cela t'aides
Voici du code javascript basé sur la réponse de nickf et le code de Bryan python. Publiez-le simplement 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();
}
Je générerais deux nombres: Longueur et Scrabble. Supposons qu'un faible score au Scrabble signifie qu'il est plus facile de rejoindre le groupe (scores faibles = beaucoup de lettres communes). Triez la liste par longueur en ordre décroissant et le Scrabble en ordre croissant.
Ensuite, il suffit de descendre la liste. Si le mot ne se 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.
Rincer et répéter, 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 que les mots croisés ne sont pas garantis pour vous, mais peut-être que quelqu'un pourra les améliorer.
J'ai réfléchi à ce problème. Mon sentiment est que pour créer des mots croisés vraiment denses, 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 laissés. Dans un trie, il est assez efficace de mettre en œuvre une traversée qui, par exemple, vous donne tous les mots de la forme "c? T".
Donc, ma pensée générale est la suivante: créez une sorte d’approche de force relativement brute, comme certains l’ont décrit ici, pour créer une croix de faible densité, et complétez les blancs avec les mots du dictionnaire.
Si quelqu'un d'autre a adopté cette approche, s'il vous plaît faites le moi savoir.
Je jouais avec le moteur du générateur de mots croisés, et j'ai trouvé cela le plus important:
0. !/usr/bin/python
une. allwords.sort(key=len, reverse=True)
b. comme le curseur, créez un élément/objet qui contournera la matrice pour faciliter l’orientation, à moins que vous ne vouliez itérer par choix aléatoire plus tard.
le premier, prenez la première paire et placez-les dans la colonne 0,0; stockez le premier en tant que notre mot de passe actuel 'leader'.
déplace le curseur en diagonale ou aléatoire avec une probabilité diagonale plus grande vers la cellule vide suivante
itérer sur les mots comme et utiliser la longueur de l'espace libre pour définir la longueur maximale du mot: 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 )
pour comparer Word à l’espace libre que j’ai utilisé, à savoir:
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
après chaque utilisation réussie de Word, changez de direction. Boucle pendant que toutes les cellules sont remplies OR vous manquez de mots OR par limite d'itérations, puis:
# CHANGE ALL WORDS LIST inexOf1stWord = allwords.index( leading_w ) allwords = allwords[:inexOf1stWord+1][:] + allwords[inexOf1stWord+1:][:]
... et réitérer de nouveaux mots croisés.
Faites le système de notation par la facilité de remplissage, et quelques calculs d'estimation. Donnez le score pour les mots croisés actuels 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.
Après la première session d’itération, effectuez une nouvelle itération dans la liste des mots croisés créés pour terminer le travail.
En utilisant plus de paramètres, la vitesse peut être considérablement améliorée.
Je voudrais obtenir un index de chaque lettre utilisée par chaque mot pour connaître les croix possibles. Ensuite, je choisirais le plus grand mot et l'utiliserais comme base. Sélectionnez le prochain grand et traversez-le. Rincer et répéter. C'est probablement un problème NP.
Une autre idée est de 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 quand il est impossible de traverser une certaine liste.
J'ai codé une solution 100% 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é:
Je vais décrire l'algorithme que j'ai utilisé:
Regroupez les mots en fonction de ceux qui partagent une lettre commune.
À 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).
Commencez les mots croisés avec le tout premier de ces blocs Word dans la position tout à fait à gauche du mots croisés.
Pour le reste des blocs Word, en partant de la position la plus à droite du mot croisé, déplacez-vous vers le haut et vers la gauche, jusqu'à ce qu'il n'y ait plus de places disponibles à remplir. S'il y a plus de colonnes vides vers le haut que vers la gauche, déplacez-vous vers le haut et inversement.