Algoritmo para generar un crucigrama

122

Dada una lista de palabras, ¿cómo haría para organizarlas en una cuadrícula de crucigramas?

No tendría que ser como un crucigrama "adecuado" que sea simétrico o algo así: básicamente solo genera una posición y dirección de inicio para cada palabra.

nickf
fuente

Respuestas:

62

Se me ocurrió una solución que probablemente no sea la más eficiente, pero funciona lo suficientemente bien. Básicamente:

  1. Ordenar todas las palabras por longitud, descendente.
  2. Tome la primera palabra y colóquela en el pizarrón.
  3. Toma la siguiente palabra.
  4. Busque en todas las palabras que ya están en la pizarra y vea si hay posibles intersecciones (letras comunes) con esta palabra.
  5. Si hay una posible ubicación para esta palabra, recorra todas las palabras que están en el tablero y verifique si la nueva palabra interfiere.
  6. Si esta palabra no rompe el tablero, colóquelo allí y vaya al paso 3; de lo contrario, continúe buscando un lugar (paso 4).
  7. Continúe este ciclo hasta que todas las palabras se coloquen o no se puedan colocar.

Esto hace un crucigrama funcional, pero a menudo bastante pobre. Hubo una serie de modificaciones que hice a la receta básica anterior para obtener un mejor resultado.

  • Al final de generar un crucigrama, califíquelo en función de cuántas palabras se colocaron (cuanto más, mejor), qué tan grande es el tablero (cuanto más pequeño, mejor) y la relación entre la altura y el ancho (cuanto más cerca a 1 el mejor). Genere una cantidad de crucigramas y luego compare sus puntajes y elija el mejor.
    • En lugar de ejecutar un número arbitrario de iteraciones, he decidido crear tantos crucigramas como sea posible en un tiempo arbitrario. Si solo tiene una pequeña lista de palabras, obtendrá decenas de crucigramas posibles en 5 segundos. Un crucigrama más grande solo se puede elegir entre 5-6 posibilidades.
  • Al colocar una nueva palabra, en lugar de ubicarla inmediatamente al encontrar una ubicación aceptable, asigne una puntuación a esa ubicación en función de cuánto aumenta el tamaño de la cuadrícula y cuántas intersecciones hay (lo ideal sería que cada palabra fuera atravesado por otras 2-3 palabras). Mantenga un registro de todas las posiciones y sus puntajes y luego elija la mejor.
nickf
fuente
77
Estoy escribiendo este programa mientras hablamos, y este es el algoritmo idéntico que elegí también. Para pequeñas cantidades de palabras (10 o menos), el programa no tiene problemas para calcular todas las soluciones posibles en milisegundos. Sin embargo, el algoritmo es exponencial. La parte fácil es escribir el algoritmo básico que fuerza a través de todas las combinaciones posibles. La parte difícil es la docena de 'atajos' que necesita para evitar que el programa pruebe todas las soluciones sin salida.
user31586
2
"5. ... y verifique si la nueva palabra interfiere" ¿Cómo explica las situaciones en las que la nueva palabra se coloca junto a una palabra existente, lo que genera galimatías en los lugares donde tiene cuadrados adyacentes? Por ejemplo: BORRADO DE LIMÓN Si "LE", "ER" y "MA" etc. no son palabras en su lista, esto está mal. Por otro lado, rechazar por completo tales adyacencias podría arrojar cuadrículas realmente buenas, como: W LEMON ERASE NEXUS TT
George Armhold
44
@Kaffeine, sí, sé lo que quieres decir: tuve que descartar estas opciones porque, aunque podrían crear cuadrículas realmente buenas, es demasiado difícil de verificar (léase: no podría molestarme) , y lo más probable es que sea solo interferencia de todos modos .
nickf
44
Construido con jQuery / Javascript usando las recomendaciones anteriores y algunas de las mías ... mlewiscs.com/crossword
MLewisCodeSolutions
@MLewisCodeSolutions Se ve increíble. ¿Eres de código abierto esto?
GKS
23

Recientemente escribí el mío en Python. Puede encontrarlo aquí: http://bryanhelmig.com/python-crossword-puzzle-generator/ . No crea los densos crucigramas de estilo NYT, sino el estilo de crucigramas que puede encontrar en el libro de rompecabezas de un niño.

A diferencia de algunos algoritmos que descubrí que implementaban un método aleatorio de fuerza bruta para colocar palabras como algunos han sugerido, traté de implementar un enfoque de fuerza bruta ligeramente más inteligente en la colocación de palabras. Aquí está mi proceso:

  1. Cree una cuadrícula de cualquier tamaño y una lista de palabras.
  2. Mezcle la lista de palabras y luego ordénelas de la más larga a la más corta.
  3. Coloque la primera y más larga palabra en la posición superior izquierda más alta, 1,1 (vertical u horizontal).
  4. Pase a la siguiente palabra, pase sobre cada letra de la palabra y cada celda en la cuadrícula buscando coincidencias de letra a letra.
  5. Cuando se encuentra una coincidencia, simplemente agregue esa posición a una lista de coordenadas sugerida para esa palabra.
  6. Pase sobre la lista de coordenadas sugerida y "califique" la ubicación de las palabras en función de cuántas otras palabras cruza. Las puntuaciones de 0 indican una ubicación incorrecta (adyacente a las palabras existentes) o que no hubo cruces de palabras.
  7. Regrese al paso 4 hasta que se agote la lista de palabras. Segundo pase opcional.
  8. Ahora deberíamos tener un crucigrama, pero la calidad puede ser impredecible debido a algunas de las ubicaciones aleatorias. Entonces, almacenamos este crucigrama y volvemos al paso 2. Si el siguiente crucigrama tiene más palabras colocadas en el tablero, reemplaza el crucigrama en el búfer. Esto es por tiempo limitado (encuentre el mejor crucigrama en x segundos).

Al final, tienes un crucigrama decente o un rompecabezas de búsqueda de palabras, ya que son casi lo mismo. Suele funcionar bastante bien, pero avíseme si tiene alguna sugerencia sobre mejoras. Las cuadrículas más grandes corren exponencialmente más lento; listas de palabras más grandes linealmente. Las listas de palabras más grandes también tienen muchas más posibilidades de obtener mejores números de colocación de palabras.

Bryan
fuente
@Neil N: Probablemente una mejor posibilidad de correspondencia de letras para las otras palabras. Quizás sería también un enfoque para ordenar por el recuento de letras distintas contenidas por palabra, lo que en su mayoría conducirá al mismo resultado.
Karl Adler
@NeilN Python array.sort(key=f)es estable, lo que significa (por ejemplo) que simplemente ordenar una lista de palabras alfabéticas por longitud mantendría todas las palabras de 8 letras ordenadas alfabéticamente.
Lynn
44
@Bryan, el enlace de tu sitio web no funciona para mí, y el dominio principal simplemente redirige a Twitter. ¿Tiene un enlace actualizado a su código?
Michael A
2
Aquí está (aparentemente) un clon del generador de Bryan: github.com/jeremy886/crossword_helmig
lvictorino
20

De hecho, escribí un programa de generación de crucigramas hace unos diez años (era críptico, pero las mismas reglas se aplicarían para los crucigramas normales).

Tenía una lista de palabras (y pistas asociadas) almacenadas en un archivo ordenado por uso descendente hasta la fecha (de modo que las palabras menos utilizadas estaban en la parte superior del archivo). Se eligió al azar una plantilla, básicamente una máscara de bits que representa los cuadrados negros y libres, de un grupo proporcionado por el cliente.

Luego, para cada palabra no completa en el rompecabezas (básicamente encuentre el primer cuadrado en blanco y vea si el que está a la derecha (palabra cruzada) o el que está debajo (palabra abajo) también está en blanco), se realizó una búsqueda de el archivo busca la primera palabra que encaja, teniendo en cuenta las letras que ya están en esa palabra. Si no había una palabra que pudiera encajar, simplemente marcó la palabra completa como incompleta y siguió adelante.

Al final habría algunas palabras incompletas que el compilador tendría que completar (y agregar la palabra y una pista al archivo si lo desea). Si no se les ocurriera ninguna idea, podrían editar el crucigrama manualmente para cambiar las restricciones o simplemente pedir una regeneración total.

Una vez que el archivo de palabra / pista alcanzó un cierto tamaño (y agregaba entre 50 y 100 pistas por día para este cliente), rara vez hubo que hacer más de dos o tres arreglos manuales para cada crucigrama. .

paxdiablo
fuente
En realidad, esto no me ayuda en mi situación, ya que tendré una lista de solo 6-12 palabras. El mío es más un ejercicio de aprendizaje para el usuario que un rompecabezas de palabras. ¡+1 para el algoritmo interesante de todos modos!
nickf
1
Buena descripción Pensé en esto algunas veces en el pasado, pero nunca lo intenté. Ahora para la pregunta mágica: ¿qué tan bien funcionó? ¿Solo para acertijos escasos o también para los densos (como en el artículo)? ¿Y cuántas pistas necesitas para acertijos densos?
dmckee --- ex-gatito moderador
1
@dmckee, fue hace un tiempo, pero de memoria, incluso los acertijos densos eran bastante buenos. Muchos se completaron sin intervención, pero aún así obtendría quizás un quinto que requiera una o dos palabras adicionales agregadas. Y estamos hablando de miles de palabras en el archivo. Sin duda, el retroceso podría haber ayudado, pero fue más fácil para el cliente rechazar una con (por ejemplo) 5 palabras sin terminar que preocuparse por tratar de encontrar pistas adicionales. Cinco era sobre el límite externo que vi para palabras inacabadas.
paxdiablo 03 de
16

Este algoritmo crea 50 crucigramas densos de flecha 6x9 en 60 segundos. Utiliza una base de datos de palabras (con palabras + consejos) y una base de datos de placas (con placas preconfiguradas).

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

¡Una base de datos de palabras más grande disminuye considerablemente el tiempo de generación y algunos tipos de tableros son más difíciles de llenar! ¡Las tablas más grandes requieren más tiempo para llenarse correctamente!


Ejemplo:

Tarjeta 6x9 preconfigurada:

(# significa una punta en una celda,% significa dos puntas en una celda, no se muestran flechas)

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

Tablero 6x9 generado:

# 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 

Consejos [línea, columna]:

[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)
tiagolr
fuente
11

Aunque esta es una pregunta anterior, intentaré una respuesta basada en un trabajo similar que he realizado.

Hay muchos enfoques para resolver problemas de restricción (que generallay están en la clase de complejidad NPC).

Esto está relacionado con la optimización combinatoria y la programación de restricciones. En este caso, las restricciones son la geometría de la cuadrícula y el requisito de que las palabras sean únicas, etc.

Los enfoques de aleatorización / recocido también pueden funcionar (aunque dentro del entorno adecuado).

¡La simplicidad eficiente podría ser la sabiduría suprema!

Los requisitos eran para un compilador de crucigramas más o menos completo y un generador (visual WYSIWYG).

Dejando a un lado la parte del constructor WYSIWYG, el resumen del compilador fue este:

  1. Cargue las listas de palabras disponibles (ordenadas por longitud de palabra, es decir, 2,3, .., 20)

  2. Encuentre los espacios de palabras (es decir, palabras de la cuadrícula) en la cuadrícula construida por el usuario (por ejemplo, palabra en x, y con longitud L, horizontal o vertical) (complejidad O (N))

  3. Calcule los puntos de intersección de las palabras de la cuadrícula (que deben completarse) (complejidad O (N ^ 2))

  4. Calcule las intersecciones de las palabras en las listas de palabras con las distintas letras del alfabeto utilizadas (esto permite buscar palabras coincidentes mediante el uso de una plantilla, por ejemplo, la tesis de Sik Cambon como la utilizada por cwc ) (complejidad O (WL * AL))

Los pasos .3 y .4 permiten hacer esta tarea:

a. Las intersecciones de las palabras de la cuadrícula con ellas mismas permiten crear una "plantilla" para tratar de encontrar coincidencias en la lista de palabras asociadas de palabras disponibles para esta palabra de la cuadrícula (mediante el uso de las letras de otras palabras de intersección con esta palabra que ya están llenas en un cierto paso del algoritmo)

si. Las intersecciones de las palabras en una lista de palabras con el alfabeto permiten encontrar palabras coincidentes (candidatas) que coinciden con una "plantilla" dada (por ejemplo, 'A' en primer lugar y 'B' en tercer lugar, etc.)

Entonces, con estas estructuras de datos implementadas, el algoritmo utilizado fue algo así:

NOTA: si la cuadrícula y la base de datos de palabras son constantes, los pasos anteriores solo se pueden hacer una vez.

  1. El primer paso del algoritmo es seleccionar un espacio de palabras vacío (palabra de cuadrícula) al azar y llenarlo con una palabra candidata de su lista de palabras asociada (la aleatorización permite producir diferentes soluciones en ejecuciones consecutivas del algoritmo) (complejidad O (1) u O ( N))

  2. Para cada espacio de palabras aún vacío (que tiene intersecciones con espacios de palabras ya llenos), calcule una razón de restricción (esto puede variar, algo simple es el número de soluciones disponibles en ese paso) y clasifique los espacios de palabras vacíos por esta razón (complejidad O (NlogN ) o O (N))

  3. Recorra los espacios de palabras vacíos calculados en el paso anterior y para cada uno intente una serie de soluciones cancdidadas (asegurándose de que "se conserve la consistencia del arco", es decir, la cuadrícula tiene una solución después de este paso si se usa esta palabra) y ordénelas de acuerdo con disponibilidad máxima para el siguiente paso (es decir, el siguiente paso tiene el máximo de soluciones posibles si esta palabra se usa en ese momento en ese lugar, etc.) (complejidad O (N * MaxCandidatesUsed))

  4. Rellene esa palabra (márquela como rellenada y vaya al paso 2)

  5. Si no se encuentra una palabra que satisfaga los criterios del paso .3, intente retroceder a otra solución candidata de algún paso anterior (los criterios pueden variar aquí) (complejidad O (N))

  6. Si se encuentra un retroceso, use la alternativa y, opcionalmente, restablezca las palabras ya rellenadas que puedan necesitar restablecerse (márquelas como no rellenadas nuevamente) (complejidad O (N))

  7. Si no se encuentra un retroceso, no se puede encontrar la solución (al menos con esta configuración, inicialización, etc.)

  8. De lo contrario, cuando todos los lotes de palabras están llenos, tienes una solución

Este algoritmo realiza un recorrido aleatorio consistente del árbol de solución del problema. Si en algún momento hay un callejón sin salida, realiza un retroceso a un nodo anterior y sigue otra ruta. Hasta que se encuentre una solución encontrada o se agote el número de candidatos para los distintos nodos.

La parte de coherencia asegura que una solución encontrada sea realmente una solución y la parte aleatoria permite producir diferentes soluciones en diferentes ejecuciones y también, en promedio, tener un mejor rendimiento.

PD. todo esto (y otros) se implementaron en la capacidad de JavaScript puro (con procesamiento paralelo y WYSIWYG)

PS2 El algoritmo se puede paralelizar fácilmente para producir más de una solución (diferente) al mismo tiempo

Espero que esto ayude

Nikos M.
fuente
1
¿Es esto para crear diseños densos (como NY Times) o diseños dispersos?
Jim
1
@ Jim, esto es principalmente para diseños densos, pero también se puede ajustar para dispersos. La diferencia está en los diseños densos (por ejemplo, clásico, escandinavo, etc.) uno tiene la cuadrícula y busca palabras, mientras que para los diseños de forma libre (disperso) tiene las palabras y busca una cuadrícula.
Nikos M.
1
¿Tendría alguna fuente de muestra disponible en algún lugar que implemente los pasos anteriores? Por ejemplo, estoy contigo la mayor parte del tiempo (y ya he implementado la mayor parte de forma independiente), pero cuando se trata de "calcular una relación de restricción ...", debo admitir que me perdiste. Hacer búsquedas en la web para cosas como "STH Ratio" tampoco me ayuda mucho. El problema con mi implementación es que tratar de encontrar las palabras para completar es muy ineficiente y toma demasiado tiempo.
Jim
1
@ Jim, ya lo tengo, ya que esto ya se usó, pero esto se hizo específicamente para un trabajo que tenía, es posible que publique una versión ligera en mis proyectos de código abierto, si necesita más ayuda, contácteme (ps en en algunos casos, el algoritmo que publiqué puede llevar demasiado tiempo, pero en promedio no)
Nikos M.
1
@ Jim, eche un vistazo a este sitio de crucigramas (todavía en progreso) istavrolexo.gr (en griego) con varios crucigramas (densos) (es decir, escandinavos, clásicos, sudoku) que han sido generados por un algoritmo similar ( un gran crucigrama escandinavo )
Nikos M.
9

¿Por qué no utilizar un enfoque probabilístico aleatorio para comenzar? Comience con una palabra, y luego elija repetidamente una palabra al azar e intente ajustarla al estado actual del rompecabezas sin romper las restricciones en el tamaño, etc. Si falla, simplemente comience de nuevo.

Te sorprenderá la frecuencia con la que funciona un enfoque de Monte Carlo como este.

Yogui
fuente
2
Sí, este es el enfoque que elegí. No tienes que intentar ser muy inteligente. Ordene palabras del más largo al más corto. En un bucle, elija una celda aleatoria (coordenada de columna y fila) y coloque la palabra en la prueba del tablero para ver si se ejecuta al final o si interfiere con otra palabra (antes de escribir la palabra en la cuadrícula, verifique que cada celda sea vacío o si tiene una letra, esa letra coincide con la letra que está intentando escribir). Hay alguna otra lógica para verificar límites y demás. La fuerza bruta genera un montón de cuadrículas cada vez más pequeñas, luego clasifico las más pequeñas según las palabras que se cruzan.
Max Hodges
6

Aquí hay un código JavaScript basado en la respuesta de nickf y el código Python de Bryan. Solo publícalo en caso de que alguien más lo necesite en 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();
}
FascistDonut
fuente
falta el esquema de objeto de palabra, proporcione wordArray
es decir, el
Literalmente, solo una serie de palabras como ['manzana', 'naranja', 'pera']
FascistDonut
Hola, para mi información, mi edición no cambió mucho código, solo lo formateó. Sé que se ve tan desordenado cuando lo veo 'en línea', pero si desea ver los cambios reales en el código, haga clic en 'reducción de lado a lado'. Bueno ... debería haber escrito "Código formateado" en la descripción de edición, pero meh.
doble pitido
¿Como funciona esto? ¿Puede proporcionar un archivo html que incorpore este javascript?
GKS
5

Generaría dos números: longitud y puntuación de Scrabble. Suponga que un puntaje bajo de Scrabble significa que es más fácil unirse (puntajes bajos = muchas letras comunes). Ordene la lista por longitud descendente y puntuación de Scrabble ascendente.

A continuación, solo baja la lista. Si la palabra no se cruza con una palabra existente (verifique cada palabra por su longitud y puntaje de Scrabble, respectivamente), luego póngala en la cola y verifique la siguiente palabra.

Enjuague y repita, y esto debería generar un crucigrama.

Por supuesto, estoy bastante seguro de que esto es O (n!) Y no se garantiza que complete el crucigrama por usted, pero tal vez alguien pueda mejorarlo.

Eric
fuente
3

He estado pensando en este problema. Mi sensación es que para crear un crucigrama realmente denso, no puede esperar que su lista de palabras limitada sea suficiente. Por lo tanto, es posible que desee tomar un diccionario y colocarlo en una estructura de datos "trie". Esto le permitirá encontrar fácilmente palabras que llenen los espacios sobrantes. En un trie, es bastante eficiente implementar un recorrido que, por ejemplo, le da todas las palabras de la forma "c? T".

Por lo tanto, mi pensamiento general es: crear algún tipo de enfoque de fuerza bruta como se describe aquí para crear una cruz de baja densidad y completar los espacios en blanco con palabras del diccionario.

Si alguien más ha adoptado este enfoque, hágamelo saber.

Jake
fuente
3

Estaba jugando con el motor generador de crucigramas, y esto me pareció lo más importante:

0.!/usr/bin/python

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

    si. haga algún elemento / objeto como el cursor que caminará alrededor de la matriz para una fácil orientación a menos que desee iterar por elección aleatoria más adelante.

  2. el primero, toma el primer par y colócalos a través de 0,0; almacenar el primero como nuestro crucigrama actual 'líder'.

  3. mover el cursor por orden diagonal o aleatorio con mayor probabilidad diagonal a la siguiente celda vacía

  4. iterar sobre las palabras me gusta y usar la longitud del espacio libre para definir la longitud máxima de la palabra: 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. para comparar la palabra con el espacio libre que usé, es decir:

    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. Después de cada palabra utilizada con éxito, cambie de dirección. Bucle mientras se llenan todas las celdas O se queda sin palabras O por límite de iteraciones y luego:

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

... e iterar de nuevo nuevo crucigrama.

  1. Haga el sistema de puntuación por facilidad de llenado y algunos cálculos de estimación. Proporcione puntaje para el crucigrama actual y limite la opción posterior agregándolo a la lista de crucigramas hechos si el sistema de puntaje satisface el puntaje.

  2. Después de la primera sesión de iteración, repita nuevamente desde la lista de crucigramas hechos para finalizar el trabajo.

Mediante el uso de más parámetros, la velocidad puede mejorarse en gran medida.

Alex
fuente
2

Obtendría un índice de cada letra utilizada por cada palabra para conocer posibles cruces. Entonces elegiría la palabra más grande y la usaría como base. Seleccione el próximo grande y crúcelo. Enjuague y repita. Probablemente sea un problema de NP.

Otra idea es crear un algoritmo genético donde la métrica de la fuerza es cuántas palabras puedes poner en la cuadrícula.

Lo difícil que encuentro es cuándo no se puede cruzar una determinada lista.

eipipuz
fuente
1
También estaba pensando en un algoritmo genético. La función de aptitud física podría ser qué tan apretadas están las palabras en la cuadrícula.
Adrian McCarthy
1

Generador de crucigramas jQuery y juego

He codificado una solución JavaScript / jQuery para este problema:

Demostración de muestra: http://www.earthfluent.com/crossword-puzzle-demo.html

Código fuente: https://github.com/HoldOffHunger/jquery-crossword-puzzle-generator

La intención del algoritmo que utilicé:

  1. Minimice el número de cuadrados inutilizables en la cuadrícula tanto como sea posible.
  2. Tener tantas palabras entremezcladas como sea posible.
  3. Calcule en un tiempo extremadamente rápido.

Demostración de un crucigrama generado.

Describiré el algoritmo que utilicé:

  1. Agrupe las palabras de acuerdo con las que comparten una letra común.

  2. A partir de estos grupos, cree conjuntos de una nueva estructura de datos ("bloques de palabras"), que es una palabra principal (que se ejecuta a través de todas las demás palabras) y luego las otras palabras (que se ejecutan a través de la palabra principal).

  3. Comience el crucigrama con el primero de estos bloques de palabras en la posición superior izquierda del crucigrama.

  4. Para el resto de los bloques de palabras, comenzando desde la posición más inferior derecha del crucigrama, muévase hacia arriba y hacia la izquierda, hasta que no haya más espacios disponibles para llenar. Si hay más columnas vacías hacia arriba que hacia la izquierda, muévase hacia arriba y viceversa.

HoldOffHunger
fuente
@holdoffhunger ¿Tiene un método para mostrar la clave de crucigramas? ¿Cajas con letras rellenas?
Jon Glazer
@ Jon Glazer: por lo general, envía las teclas de crucigramas a la función en sí, pero puede registrar el crucigrama como una matriz de caracteres en 2D, justo después var crosswords = generateCrosswordBlockSources(puzzlewords);. Solo consola registre este valor. No olvides que hay un "modo trampa" en el juego, en el que puedes hacer clic en "Revelar respuesta" para obtener el valor de inmediato.
HoldOffHunger
Esto genera acertijos con palabras "cruzadas" entrecortadas en lugares con cuadros adyacentes "hacia abajo", y viceversa. Crucigramas estándar no funcionan de esta manera, a pesar de que hace maximizar la densidad.
Beejor
1

Este aparece como un proyecto en el curso AI CS50 de Harvard. La idea es formular el problema de generación de crucigramas como un problema de satisfacción de restricciones y resolverlo con retroceso con diferentes heurísticas para reducir el espacio de búsqueda.

Para comenzar necesitamos un par de archivos de entrada:

  1. La estructura del crucigrama (que se parece a la siguiente, por ejemplo, donde el '#' representa los caracteres que no deben rellenarse y '_' representa los caracteres que deben rellenarse)

``

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

``

  1. Un vocabulario de entrada (lista de palabras / diccionario) del que se elegirán las palabras candidatas (como la que se muestra a continuación).

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

Ahora el CSP está definido y se resolverá de la siguiente manera:

  1. Las variables se definen para tener valores (es decir, sus dominios) de la lista de palabras (vocabulario) proporcionadas como entrada.
  2. Cada variable se representa mediante una tupla 3: (grid_coordinate, direction, length) donde la coordenada representa el comienzo de la palabra correspondiente, la dirección puede ser horizontal o vertical y la longitud se define como la longitud de la palabra que será la variable asignado a.
  3. Las restricciones están definidas por la entrada de estructura proporcionada: por ejemplo, si una variable horizontal y una vertical tienen un carácter común, se representará como una restricción de superposición (arco).
  4. Ahora, los algoritmos de consistencia de nodo y de consistencia de arco AC3 se pueden usar para reducir los dominios.
  5. Luego, retroceda para obtener una solución (si existe) para el CSP con MRV (valor restante mínimo), grado, etc. La heurística se puede usar para seleccionar la siguiente variable no asignada y las heurísticas como LCV (valor de restricción mínimo) se pueden usar para el dominio. ordenar, para hacer que el algoritmo de búsqueda sea más rápido.

A continuación se muestra el resultado que se obtuvo utilizando una implementación del algoritmo de resolución 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█

``

La siguiente animación muestra los pasos de retroceso:

ingrese la descripción de la imagen aquí

Aquí hay otro con una lista de palabras en idioma bengalí (bengalí):

ingrese la descripción de la imagen aquí

Sandipan Dey
fuente
+1 para una explicación realmente interesante. Sin embargo, el contexto del problema que estaba tratando de resolver aquí era que había un pequeño conjunto de palabras que todas debían usarse, y estaba tratando de encontrar un diseño óptimo para el crucigrama, en lugar de comenzar con un diseño y buscar palabras Que encaja.
nickf