Algoritmo per generare un cruciverba


123

Dato un elenco di parole, come faresti a disporle in una griglia di parole crociate?

Non dovrebbe essere come un "vero e proprio" cruciverba simmetrico o qualcosa del genere: fondamentalmente emetti solo una posizione iniziale e una direzione per ogni parola.

Risposte:


62

Ho trovato una soluzione che probabilmente non è la più efficiente, ma funziona abbastanza bene. Fondamentalmente:

  1. Ordina tutte le parole per lunghezza, decrescente.
  2. Prendi la prima parola e mettila sulla lavagna.
  3. Prendi la parola successiva.
  4. Cerca tra tutte le parole che sono già sulla lavagna e vedi se ci sono possibili intersezioni (eventuali lettere comuni) con questa parola.
  5. Se esiste una possibile posizione per questa parola, scorrere tutte le parole che si trovano sulla lavagna e verificare se la nuova parola interferisce.
  6. Se questa parola non rompe il tabellone, posizionala lì e vai al passaggio 3, altrimenti continua a cercare un luogo (passaggio 4).
  7. Continua questo ciclo finché tutte le parole non sono posizionate o non possono essere posizionate.

Questo è un cruciverba funzionante, ma spesso piuttosto scadente. Ci sono state una serie di modifiche che ho apportato alla ricetta di base sopra per ottenere un risultato migliore.

  • Alla fine della generazione di un cruciverba, assegnagli un punteggio in base a quante parole sono state posizionate (più sono, meglio è), quanto è grande la lavagna (più piccola è meglio) e il rapporto tra altezza e larghezza (più vicino a 1 meglio è). Genera una serie di cruciverba, quindi confronta i loro punteggi e scegli il migliore.
    • Invece di eseguire un numero arbitrario di iterazioni, ho deciso di creare il maggior numero possibile di cruciverba in un periodo di tempo arbitrario. Se hai solo un piccolo elenco di parole, otterrai dozzine di possibili cruciverba in 5 secondi. Un cruciverba più grande può essere scelto solo tra 5-6 possibilità.
  • Quando si inserisce una nuova parola, invece di posizionarla immediatamente dopo aver trovato una posizione accettabile, assegnare a quella parola un punteggio basato su quanto aumenta la dimensione della griglia e su quante intersezioni ci sono (idealmente vorresti che ogni parola fosse attraversato da 2-3 altre parole). Tieni traccia di tutte le posizioni e dei loro punteggi, quindi scegli la migliore.

7
Mi capita di scrivere questo programma mentre parliamo, e anche questo è lo stesso algoritmo che ho scelto. Per un piccolo numero di parole (10 o meno), il programma non ha problemi a calcolare tutte le possibili soluzioni in millisecondi. L'algoritmo è però esponenziale. La parte facile è scrivere l'algoritmo di base che forza brute attraverso tutte le possibili combinazioni. La parte difficile sono le dozzine di "scorciatoie" necessarie per impedire al programma di provare tutte le soluzioni senza uscita.
user31586

2
"5. ... e controlla se la nuova parola interferisce" Come si rendono conto delle situazioni in cui la nuova parola è posta accanto a una parola esistente, che genera incomprensioni nei punti in cui ha quadrati adiacenti? Ad esempio: LEMON ERASE Se "LE", "ER" e "MA" ecc. Non sono parole nella tua lista, è sbagliato. D'altra parte, rifiutare apertamente tali adiacenze potrebbe gettare via griglie davvero buone, come: W LEMON ERASE NEXUS TT
George Armhold

4
@ Kaffeine, sì, so cosa intendi: ho dovuto eliminare queste opzioni perché anche se potevano creare griglie davvero buone, è troppo difficile da controllare (leggi: non potrei essere disturbato) , e comunque è probabile che sia solo un'interferenza .
nickf

4
Costruito con jQuery / Javascript utilizzando i consigli di cui sopra e alcuni dei miei ... mlewiscs.com/crossword
MLewisCodeSolutions

@MLewisCodeSolutions sembra fantastico. Stai open-source questo?
GKS

23

Di recente ho scritto il mio in Python. Puoi trovarlo qui: http://bryanhelmig.com/python-crossword-puzzle-generator/ . Non crea i densi cruciverba in stile NYT, ma lo stile dei cruciverba che potresti trovare nel libro di puzzle di un bambino.

A differenza di alcuni algoritmi che ho scoperto che implementavano un metodo di forza bruta casuale per posizionare le parole come alcuni hanno suggerito, ho cercato di implementare un approccio di forza bruta leggermente più intelligente al posizionamento delle parole. Ecco il mio processo:

  1. Crea una griglia di qualsiasi dimensione e un elenco di parole.
  2. Mescola l'elenco delle parole, quindi ordina le parole dalla più lunga alla più breve.
  3. Posiziona la prima parola e quella più lunga nella posizione più in alto a sinistra, 1,1 (verticale o orizzontale).
  4. Passa alla parola successiva, ripeti ogni lettera della parola e ogni cella della griglia cercando le corrispondenze da lettera a lettera.
  5. Quando viene trovata una corrispondenza, aggiungi semplicemente quella posizione a un elenco di coordinate suggerito per quella parola.
  6. Fai scorrere l'elenco delle coordinate suggerito e "segna" la posizione della parola in base al numero di altre parole che incrocia. I punteggi pari a 0 indicano un cattivo posizionamento (adiacente a parole esistenti) o l'assenza di croci di parole.
  7. Torna al passaggio 4 fino all'esaurimento dell'elenco delle parole. Secondo passaggio opzionale.
  8. Ora dovremmo avere un cruciverba, ma la qualità può essere incostante a causa di alcuni posizionamenti casuali. Quindi, bufferizziamo questo cruciverba e torniamo al passaggio 2. Se il cruciverba successivo ha più parole sulla lavagna, sostituisce il cruciverba nel buffer. Questo è limitato nel tempo (trova il miglior cruciverba in x secondi).

Alla fine, hai un cruciverba decente o un puzzle di ricerca di parole, dal momento che sono più o meno gli stessi. Tende a funzionare piuttosto bene, ma fammi sapere se hai suggerimenti per migliorare. Le griglie più grandi funzionano esponenzialmente più lentamente; elenchi di parole più grandi in modo lineare. Gli elenchi di parole più grandi hanno anche una probabilità molto maggiore di ottenere numeri di posizionamento delle parole migliori.


@ Neil N: Probabilmente una migliore possibilità di corrispondenza delle lettere per le altre parole. Sarebbe forse anche un approccio per ordinare in base al conteggio di lettere distinte contenute per parola, che porterà principalmente allo stesso risultato.
Karl Adler

@NeilN Python array.sort(key=f)è stabile, il che significa (ad esempio) che semplicemente ordinare un elenco di parole alfabetico per lunghezza manterrebbe tutte le parole di 8 lettere ordinate alfabeticamente.
Lynn

4
@Bryan, il link del tuo sito web non funziona per me e il dominio principale reindirizza solo a Twitter. Hai un collegamento aggiornato al tuo codice?
Michael A

2
Ecco (apparentemente) un clone del generatore di Bryan: github.com/jeremy886/crossword_helmig
lvictorino

20

In realtà ho scritto un programma per la generazione di cruciverba circa dieci anni fa (era criptico ma le stesse regole si applicano ai normali cruciverba).

Aveva un elenco di parole (e indizi associati) memorizzati in un file ordinato in base all'uso decrescente fino ad oggi (in modo che le parole meno usate fossero all'inizio del file). Un modello, fondamentalmente una maschera di bit che rappresenta i quadrati neri e liberi, è stato scelto a caso da un pool fornito dal cliente.

Quindi, per ogni parola non completa nel puzzle (fondamentalmente trova il primo quadrato vuoto e vedi se anche quello a destra (parola trasversale) o quello sotto (parola in basso) è vuoto), è stata eseguita una ricerca di il file cercando la prima parola che si adattava, tenendo conto delle lettere già presenti in quella parola. Se non c'era una parola adatta, hai semplicemente contrassegnato l'intera parola come incompleta e sei andato avanti.

Alla fine ci sarebbero alcune parole non completate che il compilatore dovrebbe compilare (e aggiungere la parola e un indizio al file se lo desidera). Se non potevano venire con eventuali idee, potrebbero modificare il cruciverba manualmente per modificare i vincoli o semplicemente chiedere per un totale ri-generazione.

Una volta che il file di parole / indizi raggiungeva una certa dimensione (e aggiungeva 50-100 indizi al giorno per questo client), raramente si verificava più di due o tre correzioni manuali per ogni cruciverba .


Questo in realtà non mi aiuta nella mia situazione, poiché avrò un elenco di sole 6-12 parole. Il mio è più simile a un esercizio di apprendimento per l'utente che a un puzzle di parole. Comunque +1 per l'interessante algoritmo!
nickf

1
Bella descrizione. Ci ho pensato alcune volte in passato, ma non l'ho mai provato. Ora per la domanda magica: quanto bene ha funzionato? Solo per puzzle sparsi, o anche per quelli densi (come nel giornale)? E quanti indizi hai bisogno per puzzle densi?
dmckee --- gattino ex moderatore,

1
@dmckee, è stato un po 'di tempo fa ma a memoria, anche i puzzle densi erano abbastanza buoni. Molti sono stati completati senza intervento, ma potresti comunque ottenere un quinto che richiede l'aggiunta di una o due parole extra. E stiamo parlando di migliaia di parole nel file. Senza dubbio il backtracking avrebbe potuto aiutare, ma era più facile per il cliente rifiutarne una con (ad esempio) 5 parole non finite che preoccuparsi di cercare di trovare indizi extra. Cinque era circa il limite esterno che vedevo per le parole non finite.
paxdiablo

16

Questo algoritmo crea 50 parole crociate con frecce 6x9 in 60 secondi. Utilizza un database di parole (con parole + suggerimenti) e un database di schede (con schede preconfigurate).

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

Un database di parole più grande riduce notevolmente il tempo di generazione e alcuni tipi di schede sono più difficili da riempire! Le schede più grandi richiedono più tempo per essere riempite correttamente!


Esempio:

Scheda 6x9 preconfigurata:

(# significa un suggerimento in una cella,% significa due suggerimenti in una cella, frecce non mostrate)

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

Scheda 6x9 generata:

# 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 

Suggerimenti [riga, colonna]:

[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

Sebbene questa sia una domanda più vecchia, tenterò una risposta basata su un lavoro simile che ho fatto.

Esistono molti approcci per risolvere i problemi di vincolo (che in generale sono nella classe di complessità NPC).

Ciò è correlato all'ottimizzazione combinatoria e alla programmazione con vincoli. In questo caso i vincoli sono la geometria della griglia e il requisito che le parole siano uniche ecc.

Anche gli approcci di randomizzazione / ricottura possono funzionare (sebbene all'interno dell'impostazione corretta).

La semplicità efficiente potrebbe essere solo l'ultima saggezza!

I requisiti erano per un compilatore di parole crociate più o meno completo e un builder (visual WYSIWYG).

Lasciando da parte la parte del costruttore WYSIWYG, lo schema del compilatore era questo:

  1. Carica le liste di parole disponibili (ordinate per lunghezza della parola, cioè 2,3, .., 20)

  2. Trova le aree di parole (cioè le parole della griglia) sulla griglia costruita dall'utente (es. Parola in x, y con lunghezza L, orizzontale o verticale) (complessità O (N))

  3. Calcola i punti di intersezione delle parole della griglia (che devono essere riempite) (complessità O (N ^ 2))

  4. Calcola le intersezioni delle parole nelle liste di parole con le varie lettere dell'alfabeto utilizzato (questo permette di cercare parole corrispondenti utilizzando un modello es. Tesi Sik Cambon come usato da cwc ) (complessità O (WL * AL))

I passaggi .3 e .4 consentono di eseguire questa operazione:

un. Le intersezioni delle parole della griglia con se stesse consentono di creare un "modello" per cercare di trovare corrispondenze nell'elenco di parole associate di parole disponibili per questa parola della griglia (utilizzando le lettere di altre parole che si intersecano con questa parola che sono già riempite in un certo passo dell'algoritmo)

b. Le intersezioni delle parole in un elenco di parole con l'alfabeto consentono di trovare parole corrispondenti (candidate) che corrispondono a un dato "modello" (es. 'A' al 1 ° posto e 'B' al 3 ° posto ecc ..)

Quindi, con queste strutture dati implementate, l'algoritmo utilizzato era qc come questo:

NOTA: se la griglia e il database delle parole sono costanti, i passaggi precedenti possono essere eseguiti una sola volta.

  1. Il primo passo dell'algoritmo è selezionare uno slot di parole vuoto (parola della griglia) a caso e riempirlo con una parola candidata dalla sua lista di parole associata (la randomizzazione consente di produrre diverse soluzioni in esecuzioni consecutive dell'algoritmo) (complessità O (1) o O ( N))

  2. Per ogni slot di parola ancora vuoto (che ha intersezioni con spazi di parole già riempiti), calcola un rapporto di vincolo (questo può variare, sth semplice è il numero di soluzioni disponibili in quel passaggio) e ordina gli slot di parole vuoti in base a questo rapporto (complessità O (NlogN ) o O (N))

  3. Scorri le fasce di parole vuote calcolate nel passaggio precedente e per ognuna prova un numero di soluzioni cancdidate (assicurandoti che "la consistenza dell'arco sia mantenuta", cioè la griglia ha una soluzione dopo questo passaggio se questa parola è usata) e ordinale in base a massima disponibilità per il passaggio successivo (ovvero il passaggio successivo ha il massimo delle soluzioni possibili se questa parola viene utilizzata in quel momento in quel luogo, ecc.) (complessità O (N * MaxCandidatesUsed))

  4. Compila quella parola (contrassegnala come riempita e vai al passaggio 2)

  5. Se non viene trovata alcuna parola che soddisfi i criteri del passaggio .3, provare a tornare indietro a un'altra soluzione candidata di qualche passaggio precedente (i criteri possono variare qui) (complessità O (N))

  6. Se viene trovato il backtrack, usa l'alternativa e facoltativamente reimposta tutte le parole già riempite che potrebbero dover essere ripristinate (contrassegnale di nuovo come non riempite) (complessità O (N))

  7. Se non viene trovato alcun backtrack, la soluzione no può essere trovata (almeno con questa configurazione, seed iniziale ecc ..)

  8. Altrimenti quando tutti i wordlots sono riempiti hai una soluzione

Questo algoritmo esegue una passeggiata coerente casuale dell'albero delle soluzioni del problema. Se a un certo punto c'è un vicolo cieco, torna a un nodo precedente e segue un altro percorso. Fino ad esaurimento di una soluzione trovata o del numero di candidati per i vari nodi.

La parte di consistenza fa in modo che una soluzione trovata sia effettivamente una soluzione e la parte casuale consente di produrre diverse soluzioni in diverse esecuzioni e anche mediamente avere prestazioni migliori.

PS. tutto questo (e altri) sono stati implementati in puro JavaScript (con elaborazione parallela e WYSIWYG)

PS2. L'algoritmo può essere facilmente parallelizzato per produrre più di una (diversa) soluzione allo stesso tempo

Spero che questo ti aiuti


1
Serve per creare layout densi (come il NY Times) o layout sparsi?
Jim

1
@ Jim, questo è principalmente per i layout densi ma può essere regolato anche per i layout sparsi. La differenza è nei layout densi (es. Classico, scandinavik ecc.) Uno ha la griglia e cerca le parole, mentre per i layout a forma libera (sparse) su ha le parole e cerca una griglia.
Nikos M.

1
Ti capiterà di avere qualche fonte di esempio disponibile da qualche parte che implementa i passaggi precedenti. Ad esempio, sono con te per la maggior parte (e ho già implementato la maggior parte in modo indipendente), ma quando si tratta di "calcolare un rapporto di vincolo ...", devo ammettere che mi hai perso. Anche fare ricerche sul web per cose come "STH Ratio" non è di grande aiuto per me. Il problema con la mia implementazione è cercare di trovare le parole da compilare è molto inefficiente e richiede troppo tempo.
Jim

1
@ Jim, l'ho già usato, ma è stato fatto specificamente per un lavoro che avevo, è possibile che pubblicherò una versione leggera sui miei progetti open source, se hai bisogno di più aiuto contattami (ps anzi all'indirizzo in alcuni casi l'algoritmo che ho pubblicato può richiedere troppo tempo, ma in media non lo fa)
Nikos M.

1
@ Jim, dai un'occhiata a questo sito di cruciverba (ancora in corso) istavrolexo.gr (in greco) con vari (densi) cruciverba (es. Scandinavik, classico, sudoku) che sono stati generati da un algoritmo simile ( un grande cruciverba scandinavik )
Nikos M.

9

Perché non utilizzare semplicemente un approccio probabilistico casuale per iniziare. Inizia con una parola, quindi scegli ripetutamente una parola a caso e cerca di adattarla allo stato corrente del puzzle senza infrangere i vincoli sulla dimensione, ecc. Se fallisci, ricomincia da capo.

Sarai sorpreso di quanto spesso funzioni un approccio Monte Carlo come questo.


2
sì, questo è l'approccio che ho scelto. Non devi cercare di essere molto intelligente. Ordina le parole dalla più lunga alla più breve. In un ciclo, scegli una cella casuale (coordinate di colonna e riga) e posiziona la parola sulla lavagna test per vedere se scorre alla fine o interferisce con un'altra parola (prima di scrivere la parola sulla griglia controlla che ogni cella sia vuoto o se ha una lettera, quella lettera corrisponde alla lettera che stai cercando di scrivere). C'è qualche altra logica per controllare i limiti e cose del genere. La forza bruta genera un gruppo di griglie sempre più piccole, quindi classifica il più piccolo in base alle parole che si intersecano.
Max Hodges

6

Ecco del codice JavaScript basato sulla risposta di nickf e sul codice Python di Bryan. Basta pubblicarlo nel caso in cui qualcun altro ne abbia bisogno in js.

function board(cols, rows) { //instantiator object for making gameboards
this.cols = cols;
this.rows = rows;
var activeWordList = []; //keeps array of words actually placed in board
var acrossCount = 0;
var downCount = 0;

var grid = new Array(cols); //create 2 dimensional array for letter grid
for (var i = 0; i < rows; i++) {
    grid[i] = new Array(rows);
}

for (var x = 0; x < cols; x++) {
    for (var y = 0; y < rows; y++) {
        grid[x][y] = {};
        grid[x][y].targetChar = EMPTYCHAR; //target character, hidden
        grid[x][y].indexDisplay = ''; //used to display index number of word start
        grid[x][y].value = '-'; //actual current letter shown on board
    }
}

function suggestCoords(word) { //search for potential cross placement locations
    var c = '';
    coordCount = [];
    coordCount = 0;
    for (i = 0; i < word.length; i++) { //cycle through each character of the word
        for (x = 0; x < GRID_HEIGHT; x++) {
            for (y = 0; y < GRID_WIDTH; y++) {
                c = word[i];
                if (grid[x][y].targetChar == c) { //check for letter match in cell
                    if (x - i + 1> 0 && x - i + word.length-1 < GRID_HEIGHT) { //would fit vertically?
                        coordList[coordCount] = {};
                        coordList[coordCount].x = x - i;
                        coordList[coordCount].y = y;
                        coordList[coordCount].score = 0;
                        coordList[coordCount].vertical = true;
                        coordCount++;
                    }

                    if (y - i + 1 > 0 && y - i + word.length-1 < GRID_WIDTH) { //would fit horizontally?
                        coordList[coordCount] = {};
                        coordList[coordCount].x = x;
                        coordList[coordCount].y = y - i;
                        coordList[coordCount].score = 0;
                        coordList[coordCount].vertical = false;
                        coordCount++;
                    }
                }
            }
        }
    }
}

function checkFitScore(word, x, y, vertical) {
    var fitScore = 1; //default is 1, 2+ has crosses, 0 is invalid due to collision

    if (vertical) { //vertical checking
        for (i = 0; i < word.length; i++) {
            if (i == 0 && x > 0) { //check for empty space preceeding first character of word if not on edge
                if (grid[x - 1][y].targetChar != EMPTYCHAR) { //adjacent letter collision
                    fitScore = 0;
                    break;
                }
            } else if (i == word.length && x < GRID_HEIGHT) { //check for empty space after last character of word if not on edge
                 if (grid[x+i+1][y].targetChar != EMPTYCHAR) { //adjacent letter collision
                    fitScore = 0;
                    break;
                }
            }
            if (x + i < GRID_HEIGHT) {
                if (grid[x + i][y].targetChar == word[i]) { //letter match - aka cross point
                    fitScore += 1;
                } else if (grid[x + i][y].targetChar != EMPTYCHAR) { //letter doesn't match and it isn't empty so there is a collision
                    fitScore = 0;
                    break;
                } else { //verify that there aren't letters on either side of placement if it isn't a crosspoint
                    if (y < GRID_WIDTH - 1) { //check right side if it isn't on the edge
                        if (grid[x + i][y + 1].targetChar != EMPTYCHAR) { //adjacent letter collision
                            fitScore = 0;
                            break;
                        }
                    }
                    if (y > 0) { //check left side if it isn't on the edge
                        if (grid[x + i][y - 1].targetChar != EMPTYCHAR) { //adjacent letter collision
                            fitScore = 0;
                            break;
                        }
                    }
                }
            }

        }

    } else { //horizontal checking
        for (i = 0; i < word.length; i++) {
            if (i == 0 && y > 0) { //check for empty space preceeding first character of word if not on edge
                if (grid[x][y-1].targetChar != EMPTYCHAR) { //adjacent letter collision
                    fitScore = 0;
                    break;
                }
            } else if (i == word.length - 1 && y + i < GRID_WIDTH -1) { //check for empty space after last character of word if not on edge
                if (grid[x][y + i + 1].targetChar != EMPTYCHAR) { //adjacent letter collision
                    fitScore = 0;
                    break;
                }
            }
            if (y + i < GRID_WIDTH) {
                if (grid[x][y + i].targetChar == word[i]) { //letter match - aka cross point
                    fitScore += 1;
                } else if (grid[x][y + i].targetChar != EMPTYCHAR) { //letter doesn't match and it isn't empty so there is a collision
                    fitScore = 0;
                    break;
                } else { //verify that there aren't letters on either side of placement if it isn't a crosspoint
                    if (x < GRID_HEIGHT) { //check top side if it isn't on the edge
                        if (grid[x + 1][y + i].targetChar != EMPTYCHAR) { //adjacent letter collision
                            fitScore = 0;
                            break;
                        }
                    }
                    if (x > 0) { //check bottom side if it isn't on the edge
                        if (grid[x - 1][y + i].targetChar != EMPTYCHAR) { //adjacent letter collision
                            fitScore = 0;
                            break;
                        }
                    }
                }
            }

        }
    }

    return fitScore;
}

function placeWord(word, clue, x, y, vertical) { //places a new active word on the board

    var wordPlaced = false;

    if (vertical) {
        if (word.length + x < GRID_HEIGHT) {
            for (i = 0; i < word.length; i++) {
                grid[x + i][y].targetChar = word[i];
            }
            wordPlaced = true;
        }
    } else {
        if (word.length + y < GRID_WIDTH) {
            for (i = 0; i < word.length; i++) {
                grid[x][y + i].targetChar = word[i];
            }
            wordPlaced = true;
        }
    }

    if (wordPlaced) {
        var currentIndex = activeWordList.length;
        activeWordList[currentIndex] = {};
        activeWordList[currentIndex].word = word;
        activeWordList[currentIndex].clue = clue;
        activeWordList[currentIndex].x = x;
        activeWordList[currentIndex].y = y;
        activeWordList[currentIndex].vertical = vertical;

        if (activeWordList[currentIndex].vertical) {
            downCount++;
            activeWordList[currentIndex].number = downCount;
        } else {
            acrossCount++;
            activeWordList[currentIndex].number = acrossCount;
        }
    }

}

function isActiveWord(word) {
    if (activeWordList.length > 0) {
        for (var w = 0; w < activeWordList.length; w++) {
            if (word == activeWordList[w].word) {
                //console.log(word + ' in activeWordList');
                return true;
            }
        }
    }
    return false;
}

this.displayGrid = function displayGrid() {

    var rowStr = "";
    for (var x = 0; x < cols; x++) {

        for (var y = 0; y < rows; y++) {
            rowStr += "<td>" + grid[x][y].targetChar + "</td>";
        }
        $('#tempTable').append("<tr>" + rowStr + "</tr>");
        rowStr = "";

    }
    console.log('across ' + acrossCount);
    console.log('down ' + downCount);
}

//for each word in the source array we test where it can fit on the board and then test those locations for validity against other already placed words
this.generateBoard = function generateBoard(seed = 0) {

    var bestScoreIndex = 0;
    var top = 0;
    var fitScore = 0;
    var startTime;

    //manually place the longest word horizontally at 0,0, try others if the generated board is too weak
    placeWord(wordArray[seed].word, wordArray[seed].displayWord, wordArray[seed].clue, 0, 0, false);

    //attempt to fill the rest of the board 
    for (var iy = 0; iy < FIT_ATTEMPTS; iy++) { //usually 2 times is enough for max fill potential
        for (var ix = 1; ix < wordArray.length; ix++) {
            if (!isActiveWord(wordArray[ix].word)) { //only add if not already in the active word list
                topScore = 0;
                bestScoreIndex = 0;

                suggestCoords(wordArray[ix].word); //fills coordList and coordCount
                coordList = shuffleArray(coordList); //adds some randomization

                if (coordList[0]) {
                    for (c = 0; c < coordList.length; c++) { //get the best fit score from the list of possible valid coordinates
                        fitScore = checkFitScore(wordArray[ix].word, coordList[c].x, coordList[c].y, coordList[c].vertical);
                        if (fitScore > topScore) {
                            topScore = fitScore;
                            bestScoreIndex = c;
                        }
                    }
                }

                if (topScore > 1) { //only place a word if it has a fitscore of 2 or higher

                    placeWord(wordArray[ix].word, wordArray[ix].clue, coordList[bestScoreIndex].x, coordList[bestScoreIndex].y, coordList[bestScoreIndex].vertical);
                }
            }

        }
    }
    if(activeWordList.length < wordArray.length/2) { //regenerate board if if less than half the words were placed
        seed++;
        generateBoard(seed);
    }
}
}
function seedBoard() {
    gameboard = new board(GRID_WIDTH, GRID_HEIGHT);
    gameboard.generateBoard();
    gameboard.displayGrid();
}

manca lo schema dell'oggetto parola, fornire wordArray
esempio il

Letteralmente solo una serie di parole come ['mela', 'arancia', 'pera']
FascistDonut

Ciao, Cordiali saluti, la mia modifica non ha cambiato molto codice, lo ha semplicemente formattato. So che sembra così disordinato quando lo visualizzi "inline", ma se vuoi vedere i veri cambiamenti nel codice, fai clic su "side-by-side-markdown". Beh ... avrei dovuto scrivere "Codice formattato" nella descrizione della modifica, ma meh.
doppio segnale acustico il

Come funziona? Potete fornire un file html che incorpori questo javascript?
GKS

5

Genererei due numeri: lunghezza e punteggio di Scrabble. Supponiamo che un punteggio di Scrabble basso significhi che è più facile partecipare (punteggi bassi = molte lettere comuni). Ordina l'elenco in base alla lunghezza decrescente e al punteggio di Scrabble crescente.

Quindi, vai in fondo alla lista. Se la parola non si incrocia con una parola esistente (controlla ogni parola in base alla lunghezza e al punteggio di Scrabble, rispettivamente), mettila in coda e controlla la parola successiva.

Risciacquare e ripetere, e questo dovrebbe generare un cruciverba.

Certo, sono abbastanza sicuro che questo sia O (n!) E non è garantito che completi il ​​cruciverba per te, ma forse qualcuno può migliorarlo.


3

Ho pensato a questo problema. La mia sensazione è che per creare un cruciverba davvero denso, non puoi sperare che il tuo elenco limitato di parole sia sufficiente. Pertanto, potresti prendere un dizionario e inserirlo in una struttura dati "trie". Ciò ti consentirà di trovare facilmente le parole che riempiono gli spazi rimasti. In un trie, è abbastanza efficiente implementare un attraversamento che, diciamo, ti dà tutte le parole della forma "c? T".

Quindi, il mio pensiero generale è: creare una sorta di approccio di forza relativamente bruta come alcuni descritti qui per creare una croce a bassa densità e riempire gli spazi vuoti con le parole del dizionario.

Se qualcun altro ha adottato questo approccio, fatemelo sapere.


3

Stavo giocando con il generatore di cruciverba e ho trovato questo il più importante:

0.!/usr/bin/python

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

    b. crea un oggetto / oggetto come un cursore che camminerà intorno alla matrice per un facile orientamento a meno che tu non voglia iterare per scelta casuale in seguito.

  2. il primo, prendi il primo paio e mettilo di traverso e in basso da 0,0; memorizzare il primo come il nostro cruciverba corrente "leader".

  3. sposta il cursore in ordine diagonale o casuale con maggiore probabilità diagonale alla successiva cella vuota

  4. itera sulle parole come e usa la lunghezza dello spazio libero per definire la lunghezza massima della parola: 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. per confrontare la parola con lo spazio libero che ho usato, ad esempio:

    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. dopo ogni parola usata con successo, cambia direzione. Ripeti il ​​ciclo mentre tutte le celle sono piene O finisci le parole O per il limite di iterazioni, quindi:

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

... e ripeti un nuovo cruciverba.

  1. Crea il sistema di punteggio con facilità di riempimento e alcuni calcoli di stima. Assegna un punteggio al cruciverba corrente e restringi la scelta successiva aggiungendolo all'elenco dei cruciverba creati se il punteggio è soddisfatto dal tuo sistema di punteggio.

  2. Dopo la prima sessione di iterazione, iterare di nuovo dall'elenco dei cruciverba creati per completare il lavoro.

Utilizzando più parametri, la velocità può essere migliorata di un fattore enorme.


2

Otterrei un indice di ogni lettera usata da ogni parola per conoscere le possibili croci. Quindi sceglierei la parola più grande e la userei come base. Seleziona il prossimo grande e attraversalo. Risciacqua e ripeti. Probabilmente è un problema NP.

Un'altra idea è creare un algoritmo genetico in cui la metrica della forza è quante parole puoi inserire nella griglia.

La parte difficile che trovo è quando sapere che un certo elenco non può essere superato.


1
Stavo anche pensando a un algoritmo genetico. La funzione fitness potrebbe essere quanto strettamente le parole sono inserite nella griglia.
Adrian McCarthy

2

Questo appare come un progetto nel corso AI CS50 di Harvard. L'idea è di formulare il problema della generazione di cruciverba come un problema di soddisfazione dei vincoli e risolverlo con il backtracking con diverse euristiche per ridurre lo spazio di ricerca.

Per iniziare abbiamo bisogno di un paio di file di input:

  1. La struttura del cruciverba (che assomiglia al seguente, ad esempio, dove il '#' rappresenta i caratteri da non riempire e '_' rappresenta i caratteri da riempire)

`

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

`

  1. Un vocabolario di input (elenco di parole / dizionario) da cui verranno scelte le parole candidate (come quella mostrata di seguito).

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

Ora il CSP è definito e da risolvere come segue:

  1. Le variabili sono definite per avere valori (cioè i loro domini) dall'elenco di parole (vocabolario) fornito come input.
  2. Ogni variabile viene rappresentata da una tupla 3: (grid_coordinate, direction, length) dove la coordinata rappresenta l'inizio della parola corrispondente, la direzione può essere orizzontale o verticale e la lunghezza è definita come la lunghezza della parola che la variabile sarà assegnato a.
  3. I vincoli sono definiti dall'input di struttura fornito: ad esempio, se una variabile orizzontale e una verticale hanno un carattere comune, verranno rappresentate come vincolo di sovrapposizione (arco).
  4. Ora, gli algoritmi di coerenza del nodo e di coerenza dell'arco AC3 possono essere utilizzati per ridurre i domini.
  5. Quindi tornare indietro per ottenere una soluzione (se esiste) al CSP con MRV (valore minimo rimanente), grado ecc. L'euristica può essere utilizzata per selezionare la variabile successiva non assegnata e l'euristica come LCV (valore minimo vincolante) può essere utilizzata per dominio- ordinamento, per rendere più veloce l'algoritmo di ricerca.

Di seguito è mostrato l'output ottenuto utilizzando un'implementazione dell'algoritmo di risoluzione 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 seguente animazione mostra i passaggi del backtracking:

inserisci qui la descrizione dell'immagine

Eccone un altro con un elenco di parole in lingua bengalese:

inserisci qui la descrizione dell'immagine


+1 per una spiegazione davvero interessante. Tuttavia, il contesto per il problema che stavo cercando di risolvere qui era che c'era un piccolo insieme di parole che devono essere usate tutte, e stavo cercando di trovare un layout ottimale per il cruciverba, piuttosto che iniziare con un layout e trovare le parole che si adattano.
nickf

1

jQuery Crossword Puzzle Generator and Game

Ho codificato una soluzione JavaScript / jQuery a questo problema:

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

Codice sorgente: https://github.com/HoldOffHunger/jquery-crossword-puzzle-generator

L'intento dell'algoritmo che ho usato:

  1. Ridurre al minimo il numero dei quadrati inutilizzabili nella griglia il più possibile.
  2. Avere quante più parole mescolate possibile.
  3. Calcola in un tempo estremamente veloce.

Dimostrazione di un cruciverba generato.

Descriverò l'algoritmo che ho usato:

  1. Raggruppa le parole in base a quelle che condividono una lettera comune.

  2. Da questi gruppi, crea insiemi di una nuova struttura dati ("blocchi di parole"), che è una parola primaria (che attraversa tutte le altre parole) e poi le altre parole (che attraversano la parola primaria).

  3. Inizia il cruciverba con il primo di questi blocchi di parole nella posizione in alto a sinistra del cruciverba.

  4. Per il resto dei blocchi di parole, partendo dalla posizione più in basso a destra del cruciverba, spostati verso l'alto e verso sinistra, finché non ci sono più spazi disponibili da riempire. Se ci sono più colonne vuote verso l'alto che verso sinistra, spostati verso l'alto e viceversa.


@holdoffhunger Hai un metodo per mostrare la chiave del cruciverba? Scatole con lettere riempite?
Jon Glazer

@ Jon Glazer: In genere, invii le chiavi del cruciverba alla funzione stessa, ma puoi registrare il cruciverba come un array 2d di caratteri, subito dopo var crosswords = generateCrosswordBlockSources(puzzlewords);. Basta registrare questo valore dalla console. Non dimenticare che c'è una "modalità cheat" nel gioco, in cui puoi semplicemente fare clic su "Mostra risposta", per ottenere immediatamente il valore.
HoldOffHunger

Questo genera enigmi con parole incomprensibili "trasversali" in luoghi con caselle "in basso" adiacenti e viceversa. Cruciverba standard non funzionano in questo modo, anche se non massimizzare la densità.
Beejor
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.