Como posso encontrar as palavras válidas em uma grade de caracteres?

12

Estou criando um jogo semelhante ao Tetris, com duas diferenças principais: a tela já começa cheia de peças (como no Puzzle Quest para Nintendo DS e PC) e cada peça individual tem uma letra. O objetivo do jogador é eliminar peças, formando palavras válidas com elas. As palavras são formadas colocando letras próximas umas das outras, em qualquer direção, exceto na diagonal.

O jogador pode mover uma linha inteira de peças para a esquerda ou para a direita ou uma coluna inteira de peças para cima ou para baixo, pelo espaço que desejar (se o movimento de uma linha / coluna ultrapassar os limites do tabuleiro, o A letra que ultrapassa o limite "circula", aparecendo no outro extremo da linha / coluna). Após a ação do jogador, o jogo deve verificar o tabuleiro inteiro para procurar palavras válidas e remover as letras que formam essas palavras do tabuleiro. As letras acima daquelas que foram removidas cairão no lugar daquelas que foram removidas e novas cartas cairão da parte superior da tela até que o quadro seja preenchido novamente.

Eu já escrevi um algoritmo linear que, dada uma sequência de caracteres, determina se é uma palavra válida em inglês. O problema que estou enfrentando é: como posso verificar se há palavras válidas no quadro? A força bruta é a única maneira? Testar todas as combinações possíveis do quadro para ver se elas são válidas é muito lento, mesmo para um quadro pequeno (5x5). Qualquer ajuda será muito apreciada, obrigado!

Tavio
fonte
Infelizmente, você está certo. É muito lento, devido aos números (todas as combinações de 3, 4, 5, ... 25 letras). Talvez restrinja-o a "as palavras devem estar alinhadas horizontal ou verticalmente" para melhorar o desempenho (e não obter palavras aleatórias que o jogador não viu)?
ashes999
Eu acho que você precisa olhar novamente para o seu algoritmo que corresponde à sequência de caracteres para palavras. Pela minha contagem, uma grade de 5x5 teria 2700 palavras em potencial, que seu algoritmo deve transmitir, veja, por exemplo, a resposta de Josh.
Taemyr 13/05
Chego às 2700 palavras da seguinte maneira; comece com as palavras da esquerda para a direita na primeira linha. Há uma posição em que uma palavra de 5 letras, 2 palavras de 4 letras, 3 palavras de 3 letras, 4 palavras de 2 letras e 5 palavras de 1 letra. Podemos trocar uma das letras da palavra por uma letra de outra coluna. Sem perder a generalidade, podemos assumir que nenhuma letra é trocada pelas palavras de 1 letra e que a primeira letra não é trocada por palavras de 2 letras. Isto dá; 5 * 5 * 1 + 4 * 5 * 2 + 3 * 5 * 3 + 1 * 5 * 4 + 1 = 135. Multiplique pelo número de linhas e direções; 135 * 5 * 4 = 2700
Taemyr
Acho que não deixei isso claro, mas as palavras podem ser formadas em qualquer direção, exceto na diagonal e até fazer cantos (por exemplo, o primeiro bloco da primeira linha, depois o segundo bloco à direita na primeira linha, seguido pelo lado a baixo na segunda linha).
Tavio 13/05
@Tavio Alguns pensamentos: a verificação deve incluir palavras mais longas primeiro (se eu colocar "de lado" eu não quero "como"). Além disso, palavras com uma letra podem ser ignoradas, caso contrário você nunca poderá usar nenhum a. quando terminar, gostaria de saber o nome que você dá este jogo para que eu possa dar uma olhada.
David Starkey

Respostas:

22

A força bruta não é o único caminho.

Resolver seu tabuleiro de jogo é semelhante a resolver um tabuleiro Boggle , exceto mais simples. Você deseja verificar todos os blocos no quadro, procurando ver se há alguma palavra que possa ser feita nas instruções apropriadas.

Você ainda deseja refinar ainda mais o seu espaço de pesquisa para não se preocupar em pesquisar ao longo de uma direção se souber que não pode fazer uma palavra. Por exemplo, se você encontrar dois qs seguidos, deverá abortar. Para esse fim, você desejará algum tipo de estrutura de dados que permita dizer se um determinado conjunto de caracteres é um prefixo de uma palavra válida. Para isso, você pode usar uma árvore trie ou prefixo; uma estrutura de dados útil ao resolver problemas como este.

Uma árvore de prefixos é uma estrutura hierárquica baseada em nó, em que cada nó representa algum prefixo de seus filhos e os nós folha (geralmente) representam os valores finais. Por exemplo, se seu dicionário de palavras válidas contiver "gato", "carro" e "célula", um trie pode se parecer com:

    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

Assim, comece preenchendo uma árvore de prefixos com todas as palavras válidas no seu jogo.

O processo real de encontrar palavras válidas no quadro a qualquer momento envolve o início de uma pesquisa recursiva de cada bloco do quadro. Como cada pesquisa no espaço do quadro, iniciando em um determinado bloco, é independente, elas podem ser paralelizadas, se necessário. Ao pesquisar, você "segue" a árvore de prefixos com base no valor da letra na direção em que está pesquisando.

Você chegará a um ponto em que nenhuma das letras ao redor é filha do nó atual da árvore do prefixo. Quando você chega nesse ponto, se também é verdade que o nó atual é uma folha, você encontrou uma palavra válida. Caso contrário, você não encontrou uma palavra válida e poderá abortar a pesquisa.

Um código de exemplo e uma discussão sobre essa técnica (e outros, como uma solução de programação dinâmica que pode ser ainda mais rápida "invertendo" o espaço de pesquisa de uma maneira) podem ser encontrados no blog desse colega aqui ; ele discute a solução do Boggle, mas adaptar as soluções ao seu jogo é mais ou menos uma questão de mudar em quais direções você permite que a pesquisa ocorra.


fonte
A força bruta não é a única maneira de você se explicar. :) Existem muitos prefixos que sugerem que não há motivo para continuar procurando. (A maioria das seqüências de caracteres [aleatórias] não são palavras. +1
AturSams
Ótima resposta. Uma "palavra" é qualquer coisa no ponto final do dicionário do jogo.
Adam Eberbach
OP afirma que ele tem um algoritmo para corresponder a palavra a uma cadeia de caracteres. Então, acho que isso não responde à pergunta.
Taemyr
OTOH Acho que o OP vai querer um algoritmo de correspondência de string mais eficiente do que o que ele possui atualmente.
Taemyr
1
@ Taemyr usando trie simples, sim. Mas alguém poderia usar o algoritmo Aho-Corasick, que utiliza um trie ligeiramente modificado, é muito mais eficaz (linear). Com o algoritmo Aho-Corasick, é possível encontrar todas as palavras válidas na matriz nxn no tempo O (n ^ 2).
El.pescado
3

Você pode ter tentado isso, já implementado isso, talvez seja melhor acompanhado por outra resposta, etc. Mas eu não os vi mencionados (ainda), então aqui está:

Você pode descartar muitas verificações acompanhando o que mudou e o que não mudou. Por exemplo:

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, no código 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 as perguntas triviais:

Você tem as otimizações de velocidade dos compiladores definidas? (se você estiver usando um)

O seu algoritmo de busca por palavras pode ser otimizado? De alguma forma?

Wolfgang Skyler
fonte
Exceto que os jogadores têm permissão para girar linhas, portanto, encontrar uma palavra na terceira coluna afetará as outras colunas.
Taemyr
@ Taemyr IF(rowMoved){ checkColumns(); checkMovedRow(); } IF(columnMoved){ checkRows() checkMovedColumn();} Se um usuário puder mover apenas um de cada vez, no final desse movimento, nenhuma letra paralela foi movida e, portanto, não será necessário verificar novamente.
David Starkey
2

Lembre-se de que todo caractere é um valor. Então use isso para sua vantagem. Existem algumas funções de hash que podem ser calculadas rapidamente ao iterar em substrings. Por exemplo, digamos que damos a cada letra um código de 5 bits (basta fazer c - 'a' + 1em C):

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

Você pode executar rapidamente todas as substrings de um determinado tamanho:

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

Podemos verificar substrings com até 12 letras dessa maneira nas arquiteturas mais comuns hoje em dia.

Se existir um código de hash no seu dicionário, você poderá extrair a palavra rapidamente, pois códigos de hash como esse são únicos. Quando você atingir o máximo de 12 letras, poderá adicionar outras estruturas de dados para palavras que começam com essas 12 letras. Se você encontrar uma palavra que comece com 12 letras específicas, basta criar uma lista ou outra pequena tabela de hash para os sufixos de cada palavra que começa com esse prefixo.

Armazenar um dicionário de todos os códigos de palavras existentes não deve levar mais do que alguns megabytes de memória.

AturSams
fonte
0

Você está limitado apenas às formas clássicas de Tetris ao formar palavras, ou será que alguma formação servirá? As palavras podem dobrar indefinidamente ou apenas uma vez? Uma palavra pode durar o quanto quiser? Isso é bastante complexo se você puder fazer as dobras que quiser, tornando efetivamente as palavras mais longas possíveis com 25 caracteres. Eu diria que você tem uma lista de palavras aceitas. Nessa suposição, sugiro que tente algo como isto:

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

Isso criará um mapa em cada bloco com informações sobre como esse bloco é conectado às palavras ao redor dele na grade. Quando uma coluna ou linha é movida, verifique todos os blocos que foram ou são adjacentes ao movimento e recalcule as informações. Quando você encontra uma palavra, não é possível adicionar mais blocos a essa palavra; remova. Não tenho certeza se isso será mais rápido, mas realmente se resume a quantas palavras são criadas pela metade. O benefício disso é que o usuário provavelmente está tentando criar uma palavra a partir de uma palavra meia completa no quadro. Mantendo todas essas palavras armazenadas, é fácil verificar se uma palavra foi concluída.

PMunch
fonte