Come posso trovare le parole valide in una griglia di caratteri?


12

Sto creando un gioco simile a Tetris, con due differenze principali: lo schermo inizia già pieno di tessere (come in Puzzle Quest per Nintendo DS e PC) e ogni singola tessera contiene una lettera. L'obiettivo del giocatore è eliminare le tessere formando parole valide con esse. Le parole si formano posizionando le lettere una accanto all'altra, in qualsiasi direzione, tranne in diagonale.

Il giocatore può spostare un'intera fila di tessere a sinistra o a destra o un'intera colonna di tessere su o giù, per tutti gli spazi che desidera (se il movimento di una fila / colonna supera i limiti del tabellone, il la lettera che attraversa il limite "scorrerà", comparendo all'altra estremità della riga / colonna). Dopo l'azione del giocatore, il gioco dovrebbe controllare l'intera scacchiera per cercare parole valide e rimuovere le lettere che formano quelle parole dal tabellone. Le lettere sopra quelle che sono state rimosse cadranno al posto di quelle che sono state rimosse e le nuove lettere scenderanno dalla parte superiore dello schermo fino a riempire nuovamente la scheda.

Ho già scritto un algoritmo lineare che, data una sequenza di caratteri, determina se è una parola inglese valida. Il problema che sto riscontrando è: come posso verificare la presenza di parole valide sulla lavagna? La forza bruta è l'unico modo? Testare tutte le possibili combinazioni dalla scheda per vedere se sono valide è molto lento, anche per una scheda piccola (5x5). Qualsiasi aiuto sarà molto apprezzato, grazie!


Sfortunatamente, hai ragione. È molto lento, a causa dei numeri (tutte le combinazioni di 3, 4, 5, ... 25 lettere). Forse limitarlo a "le parole devono essere allineate orizzontalmente o verticalmente" per migliorare le prestazioni (e non ottenere parole casuali che il giocatore non ha visto)?
ashes999,

Penso che devi rivedere il tuo algoritmo che abbina la sequenza di caratteri alle parole. Secondo il mio conto, una griglia 5x5 avrebbe 2700 parole potenziali, che il tuo algoritmo dovrebbe superare, vedi ad esempio la risposta di Josh.
Taemyr,

Arrivo alle 2700 parole nel modo seguente; inizia con le parole da sinistra a destra sulla prima riga. Ci sono 1 posizione che una parola di 5 lettere, 2 parole di 4 lettere, 3 parole di 3 lettere, 4 parole di 2 lettere e 5 parole di 1 lettera. Possiamo scambiare una delle lettere della parola con una lettera di un'altra colonna. Possiamo, senza perdere la generalità, supporre che nessuna lettera venga scambiata con le parole di 1 lettera e che la prima lettera non venga scambiata con parole di 2 lettere. Questo da; 5 * 5 * 1 + 4 * 5 * 2 + 3 * 5 * 3 + 1 * 5 * 4 + 1 = 135. Moltiplicare per numero di righe e direzioni; 135 * 5 * 4 = 2700
Taemyr,

Penso di non averlo chiarito, ma le parole possono essere formate in qualsiasi direzione, tranne in diagonale, e persino di creare degli angoli (ad esempio, prima tessera dalla prima fila, quindi seconda tessera a destra nella prima fila, seguita dal tessera dal basso nella seconda fila).
Tavio,

@Tavio Alcune considerazioni: il controllo dovrebbe prima andare parole più lunghe (se faccio "da parte" non voglio "come". Inoltre, le parole a una lettera potrebbero essere ignorate meglio, altrimenti non sarai mai in grado di usare le a. Al termine, vorrei sapere il nome che dai a questo gioco in modo da poterlo controllare.
David Starkey,

Risposte:


22

La forza bruta non è l'unico modo.

Risolvere il tuo tabellone di gioco è simile alla risoluzione di un tabellone Boggle , tranne che più semplice. Vuoi controllare ogni tessera nel tabellone, cercando di vedere se ci sono parole che possono essere fatte lungo le direzioni appropriate.

Ti piacerebbe ancora affinare ulteriormente il tuo spazio di ricerca in modo da non preoccuparti di cercare in una direzione se sai che non puoi fare una parola. Ad esempio, se trovi due qs di fila, dovresti interrompere. A tal fine, vorrai una sorta di struttura di dati che ti permetta di dire se un determinato set di caratteri è un prefisso di una parola valida. Per questo, puoi usare un albero trie o prefisso; un'utile struttura di dati per risolvere problemi come questo.

Un albero di prefisso è una struttura gerarchica basata su nodi, in cui ogni nodo rappresenta un prefisso dei suoi figli e i nodi foglia (in genere) rappresentano i valori finali. Ad esempio, se il tuo dizionario di parole valide contiene "cat", "car" e "cell", un trie potrebbe apparire come:

    0        The root of the trie is the empty string here, although you 
    |        can implement the structure differently if you want.
    c
   / \ 
  a   e
 / \   \
t  r    l
         \
          l

Quindi, inizia riempiendo un albero di prefissi con ogni parola valida nel tuo gioco.

Il processo effettivo di ricerca di parole valide sul tabellone in qualsiasi momento comporterà l'avvio di una ricerca ricorsiva da ogni riquadro del tabellone. Poiché ogni ricerca nello spazio del tabellone a partire da una determinata tessera è indipendente, queste possono essere parallelizzate se necessario. Durante la ricerca, "segui" l'albero del prefisso in base al valore della lettera nella direzione in cui stai cercando.

Alla fine raggiungerai un punto in cui nessuna delle lettere circostanti è figlia del tuo attuale nodo dell'albero dei prefissi. Quando raggiungi quel punto, se è anche vero che il nodo corrente è una foglia, hai trovato una parola valida. Altrimenti, non hai trovato una parola valida e potresti interrompere la ricerca.

Codice di esempio e una discussione di questa tecnica (e di altri, come una soluzione di programmazione dinamica che può essere ancora più veloce "invertendo" lo spazio di ricerca dopo una moda) può essere trovata qui sul blog di questo collega ; discute di come risolvere Boggle, ma adattare le soluzioni al tuo gioco è più o meno una questione di cambiare le direzioni in cui permetti di cercare.


La forza bruta non è l'unico modo come ti sei spiegato. :) Ci sono molti prefissi che suggeriscono che non ha senso continuare a cercare. (La maggior parte delle stringhe [casuali] non sono parole. +1
AturSams

Bella risposta. Una "parola" è qualsiasi cosa nel dizionario del gioco.
Adam Eberbach,

OP afferma che ha un algoritmo per abbinare la parola a una stringa di caratteri. Quindi non penso che questo risponda alla domanda.
Taemyr,

OTOH Penso che OP vorrà un algoritmo di corrispondenza delle stringhe più efficiente di quello che ha attualmente.
Taemyr,

1
@Taemyr usando il semplice trie, sì. Ma si potrebbe usare l'algoritmo Aho-Corasick che utilizza un trie leggermente modificato è molto più efficace (lineare). Con l'algoritmo Aho-Corasick si possono trovare tutte le parole valide nella matrice nxn nel tempo O (n ^ 2).
el.pescado,

3

Avresti potuto provare questo, già implementato, forse meglio accompagnato da un'altra risposta, ecc. Ma non li ho visti menzionati (ancora), quindi eccolo:

Puoi scartare molti dei controlli tenendo traccia di cosa è cambiato e cosa no. Per esempio:

On a 5x5 field, A vertical word is found on base of the third column,
All the rows change. However, the first, second, fourth, and fifth,
columns do not change, so you dont need to worry about them (the third did change.)

On a 5x5 field, A 3 letter word is found horizontally on row 2, column 3, to column 5.
So you need to check row 1 and 2 (row 1 because the words on that one
fell down and where replaced), as-well as columns 3, 4, and 5.

o, in codice psudo

// update the board

// and check
if (vertical_word)
{
    check(updated_column)

    for (i in range 0 to updated_row_base)
        check(i)
}
else // horizontal word
{
    for (i in range 0 to updated_row)
        check(i)

    for (i in range 0 to updated_column_start)
        check(i)

    for (i in range updated_column_end+1 to final_column)
        check(i)
}

E le domande banali:

Hai l'ottimizzazione della velocità dei compilatori impostata? (se ne usi uno)

Il tuo algoritmo di ricerca delle parole può essere ottimizzato? In ogni modo?


Tranne che ai giocatori è permesso ruotare le righe, quindi trovare una parola nella terza colonna influenzerà le altre colonne.
Taemyr,

@Taemyr IF(rowMoved){ checkColumns(); checkMovedRow(); } IF(columnMoved){ checkRows() checkMovedColumn();} Se un utente può spostarsi solo uno alla volta, quindi alla fine di quella mossa, non si sono mosse lettere parallele e quindi non è necessario ricontrollarle.
David Starkey,

2

Ricorda che ogni personaggio ha un valore. Quindi usalo a tuo vantaggio. Esistono alcune funzioni hash che potrebbero essere calcolate rapidamente durante l'iterazione su sottostringhe. Ad esempio, supponiamo che diamo ad ogni lettera un codice a 5 bit (basta fare c - 'a' + 1in C):

space = 00000;
a = 00001;
c = 00011;
e = 00101;
t = 01100;

Di quanto potresti rapidamente passare su tutte le sottostringhe di una certa dimensione:

a b [c a t] e t h e = 00011 00001 01100;
//Now we just remove the 5 msb and shfit the rest 5 bits left and add the new character.
a b  c [a t e] t h e = (([c a t] & 0xffff) << 5) | e; // == a t e

Oggi possiamo controllare sottostringhe fino a 12 lettere sulle architetture più comuni.

Se nel tuo dizionario esiste un codice hash, puoi estrarre rapidamente la parola da lì perché i codici hash come questo sono univoci. Quando raggiungi il massimo di 12 lettere, potresti voler aggiungere un'altra struttura di dati per le parole che iniziano con quelle 12 lettere. Se trovi una parola che inizia con una specifica 12 lettere, crea semplicemente un elenco o un'altra minuscola tabella hash per i suffissi di ogni parola che inizia con quel prefisso.

La memorizzazione di un dizionario di tutti i codici di parole esistenti non dovrebbe richiedere più di qualche megabyte di memoria.


0

Sei limitato alle forme classiche di Tetris quando formi parole, o ci sarà una formazione? Le parole possono piegarsi indefinitamente o solo una volta? Una parola può essere lunga quanto vuole? Questo diventa abbastanza complesso se riesci a fare tutte le curve che vuoi, rendendo le parole più lunghe possibili lunghe 25 caratteri. Suppongo che tu abbia un elenco di parole accettate. Su tale presupposto ti suggerisco di provare qualcosa del genere:

At the start of the game:
  Iterate tiles:
    Use tile as starting letter
      Store previous tile
      Check the four adjacent tiles
      If a tile can continue a word started by the previous tile, carry on
      Store the next tile
      Move check to next tile

Ciò creerà una mappa su ogni riquadro con le informazioni su come questo riquadro è collegato alle parole che lo circondano nella griglia. Quando una colonna o una riga viene spostata, controlla tutte le tessere che sono state o sono adiacenti al movimento e ricalcola le informazioni. Quando trovi una parola e non è possibile aggiungere più tessere a quella parola; rimuoverlo. Non sono sicuro che questo sarà più veloce, si riduce davvero a quante parole sono create a metà. Il vantaggio di ciò è che l'utente probabilmente sta cercando di creare una parola da una parola mezza completa sulla lavagna. Conservando tutte queste parole memorizzate, è facile controllare se una parola è stata completata.

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.