Comment trouver les mots valides dans une grille de caractères?

12

Je crée un jeu similaire à Tetris, avec deux différences principales: l'écran commence déjà rempli de tuiles (comme dans Puzzle Quest pour Nintendo DS et PC) et chaque tuile individuelle a une lettre. L'objectif du joueur est d'éliminer les tuiles en formant avec eux des mots valides. Les mots sont formés en plaçant les lettres côte à côte, dans n'importe quelle direction, sauf en diagonale.

Le joueur peut déplacer une rangée entière de tuiles vers la gauche ou vers la droite ou une colonne entière de tuiles vers le haut ou vers le bas, pour autant d'espaces qu'il le souhaite (si le mouvement d'une rangée / colonne dépasse les limites du plateau, le lettre qui franchit la limite "cycle", apparaissant à l'autre bout de la ligne / colonne). Après l'action du joueur, le jeu doit vérifier l'ensemble du tableau pour rechercher des mots valides et supprimer les lettres qui forment ces mots du tableau. Les lettres au-dessus de celles qui ont été supprimées tomberont à la place de celles qui ont été supprimées et les nouvelles lettres tomberont du haut de l'écran jusqu'à ce que le tableau soit à nouveau rempli.

J'ai déjà écrit un algorithme linéaire qui, étant donné une séquence de caractères, détermine s'il s'agit d'un mot anglais valide. Le problème que je rencontre est le suivant: comment puis-je rechercher des mots valides au tableau? La force brute est-elle le seul moyen? Tester toutes les combinaisons possibles à partir de la carte pour voir si elles sont valides est très lent, même pour une petite carte (5x5). Toute aide sera très appréciée, merci!

Tavio
la source
Malheureusement, vous avez raison. C'est très lent, à cause des chiffres (toutes combinaisons de 3, 4, 5, ... 25 lettres). Peut-être le limiter à "les mots doivent être alignés horizontalement ou verticalement" pour améliorer les performances (et ne pas obtenir des mots aléatoires que le joueur n'a pas vus)?
ashes999
Je pense que vous devez revoir votre algorithme qui fait correspondre la séquence de caractères aux mots. D'après mes calculs, une grille 5x5 aurait 2700 mots potentiels, que votre algorithme devrait traverser, voir par exemple la réponse de Josh.
Taemyr
J'arrive aux 2700 mots de la manière suivante; commencez par les mots de gauche à droite sur la première ligne. Il y a 1 position pour un mot de 5 lettres, 2 mots de 4 lettres, 3 mots de 3 lettres, 4 mots de 2 lettres et 5 mots de 1 lettre. Nous pouvons échanger l'une des lettres du mot contre une lettre d'une autre colonne. On peut sans perdre de généralité supposer qu'aucune lettre n'est échangée pour les mots à 1 lettre, et que la première lettre n'est pas échangée pour les mots à 2 lettres. Cela donne; 5 * 5 * 1 + 4 * 5 * 2 + 3 * 5 * 3 + 1 * 5 * 4 + 1 = 135. Multipliez par le nombre de lignes et de directions; 135 * 5 * 4 = 2700
Taemyr
Je pense que je n'ai pas précisé cela, mais les mots peuvent être formés dans n'importe quelle direction, sauf en diagonale, et même faire des coins (par exemple, la première tuile de la première rangée, puis la deuxième tuile à droite sur la première rangée, suivie de la tuile par le bas sur la deuxième rangée).
Tavio
@Tavio Quelques réflexions: la vérification devrait aller plus longtemps en premier (si je mets "de côté", je ne veux pas "comme". De plus, les mots d'une seule lettre pourraient être mieux ignorés, sinon vous ne pourrez jamais utiliser de a. Une fois terminé, j'aimerais connaître le nom que vous donnez à ce jeu afin que je puisse le vérifier.
David Starkey

Réponses:

22

La force brute n'est pas le seul moyen.

Résoudre votre plateau de jeu est similaire à la résolution d'un plateau Boggle , sauf plus simple. Vous voulez vérifier chaque tuile du tableau, en cherchant s'il y a des mots qui peuvent être faits le long des directions appropriées.

Vous souhaitez toujours affiner votre espace de recherche afin de ne pas vous embêter à chercher dans une direction si vous savez que vous ne pouvez pas faire un mot. Par exemple, si vous trouvez deux qs d'affilée, vous devez abandonner. À cette fin, vous aurez besoin d'une sorte de structure de données qui vous permet de dire si un ensemble de caractères donné est un préfixe d'un mot valide. Pour cela, vous pouvez utiliser un arbre de trie ou de préfixe; une structure de données utile lors de la résolution de problèmes comme celui-ci.

Un arbre de préfixe est une structure hiérarchique basée sur des nœuds, où chaque nœud représente un préfixe de ses enfants, et les nœuds feuilles (généralement) représentent les valeurs finales. Par exemple, si votre dictionnaire de mots valides contient "chat", "voiture" et "cellule", un trie pourrait ressembler à:

    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

Ainsi, commencez par remplir un arbre de préfixe avec chaque mot valide de votre jeu.

Le processus réel de recherche de mots valides sur le tableau à un moment donné impliquera de lancer une recherche récursive à partir de chaque tuile du tableau. Étant donné que chaque recherche dans l'espace du tableau à partir d'une tuile donnée est indépendante, celles-ci peuvent être parallélisées si nécessaire. Lorsque vous effectuez une recherche, vous "suivez" l'arborescence des préfixes en fonction de la valeur de la lettre dans la direction que vous recherchez.

Vous finirez par atteindre un point où aucune des lettres environnantes n'est enfant de votre nœud d'arbre de préfixe actuel. Lorsque vous atteignez ce point, s'il est également vrai que le nœud actuel est une feuille, vous avez trouvé un mot valide. Sinon, vous n'avez pas trouvé de mot valide et vous pouvez abandonner la recherche.

Un exemple de code et une discussion de cette technique (et d'autres, comme une solution de programmation dynamique qui peut être encore plus rapide en "inversant" l'espace de recherche d'une manière) peuvent être trouvés sur le blog de ce boursier ici ; il discute de la résolution de Boggle, mais l'adaptation des solutions à votre jeu consiste plus ou moins à changer les directions dans lesquelles vous autorisez la recherche.


la source
La force brute n'est pas le seul moyen comme vous l'avez expliqué vous-même. :) Il y a beaucoup de préfixes qui suggèrent qu'il est inutile de continuer à chercher. (La plupart des chaînes [aléatoires] ne sont pas des mots. +1
AturSams
Très bonne réponse. Un "mot" est n'importe quoi dans le dictionnaire du jeu.
Adam Eberbach
OP indique qu'il a un algorithme pour faire correspondre le mot à une chaîne de caractères. Je ne pense donc pas que cela réponde à la question.
Taemyr
OTOH Je pense que OP voudra un algorithme de correspondance de chaînes plus efficace que ce qu'il a actuellement.
Taemyr
1
@Taemyr utilisant un trie simple, oui. Mais on pourrait utiliser l'algorithme Aho-Corasick qui utilise un trie légèrement modifié est beaucoup plus efficace (linéaire). Avec l'algorithme Aho-Corasick, on peut trouver tous les mots valides dans la matrice nxn en temps O (n ^ 2).
el.pescado
3

Vous avez peut-être essayé cela, déjà mis en œuvre cela, peut-être mieux accompagné d'une autre réponse, etc. Mais je ne les ai pas vu mentionnés (encore), alors voici:

Vous pouvez jeter un grand nombre de chèques en gardant une trace de ce qui a changé et de ce qui n'a pas changé. Par exemple:

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.

ou, en code 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)
}

Et les questions triviales:

Avez-vous défini les optimisations de vitesse des compilateurs? (si vous en utilisez un)

Votre algorithme de recherche de mots peut-il être optimisé du tout? De quelque manière que?

Wolfgang Skyler
la source
Sauf que les joueurs sont autorisés à faire pivoter les lignes, donc trouver un mot dans la troisième colonne affectera les autres colonnes.
Taemyr
@Taemyr IF(rowMoved){ checkColumns(); checkMovedRow(); } IF(columnMoved){ checkRows() checkMovedColumn();} Si un utilisateur ne peut se déplacer qu'un par un, alors à la fin de ce déplacement, aucune lettre parallèle ne s'est déplacée et donc pas besoin de les revérifier.
David Starkey
2

N'oubliez pas que chaque caractère est une valeur. Utilisez donc cela à votre avantage. Certaines fonctions de hachage peuvent être calculées rapidement lors de l'itération sur des sous-chaînes. Par exemple, disons que nous donnons à chaque lettre un code 5 bits (il suffit de le faire c - 'a' + 1en C):

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

Que vous pourriez rapidement exécuter sur toutes les sous-chaînes d'une certaine taille:

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

Nous pouvons vérifier les sous-chaînes jusqu'à 12 lettres de cette façon sur les architectures les plus courantes aujourd'hui.

Si un code de hachage existe dans votre dictionnaire, vous pouvez en tirer rapidement le mot car les codes de hachage comme celui-ci sont uniques. Lorsque vous atteignez le maximum de 12 lettres, vous souhaiterez peut-être ajouter une autre structure de données pour les mots commençant par ces 12 lettres. Si vous trouvez un mot qui commence par 12 lettres spécifiques, créez simplement une liste ou une autre petite table de hachage pour les suffixes de chaque mot commençant par ce préfixe.

Le stockage d'un dictionnaire de tous les codes de mots existants ne devrait pas prendre plus de quelques mégaoctets de mémoire.

AturSams
la source
0

Êtes-vous limité uniquement aux formes classiques de Tetris lors de la formation des mots, ou toute formation fera-t-elle? Les mots peuvent-ils se plier indéfiniment ou une seule fois? Un mot peut-il être aussi long qu'il le souhaite? Cela devient assez complexe si vous pouvez faire autant de virages que vous le souhaitez, ce qui rend les mots les plus longs possibles de 25 caractères. Je suppose que vous avez une liste de mots acceptés. Dans cette hypothèse, je vous suggère d'essayer quelque chose comme ceci:

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

Cela créera une carte sur chaque tuile avec des informations sur la façon dont cette tuile est connectée aux mots qui l'entourent dans la grille. Lorsqu'une colonne ou une ligne est déplacée, vérifiez toutes les tuiles qui ont été ou sont adjacentes au mouvement et recalculez les informations. Lorsque vous trouvez un mot, plus aucune tuile ne peut être ajoutée à ce mot; le retirer. Je ne sais pas si cela sera plus rapide, cela se résume vraiment au nombre de mots à moitié créés. L'avantage est que l'utilisateur essaie très probablement de créer un mot à partir d'un mot à moitié complet sur le tableau. En gardant tous ces mots stockés, il est facile de vérifier si un mot a été complété.

PMunch
la source