Como melhorar essa geração de números aleatórios no meu contexto?

11

No meu jogo, há uma palavra no topo da tela, as letras estão chovendo de cima e o usuário precisa tocar nas letras para completar a palavra.

Atualmente, estou gerando letras aleatoriamente (na verdade, números e números aleatórios são o índice para a matriz de letras. Por exemplo: 0 = a, 1 = b), mas o problema é que leva muito tempo para obter todas as letras necessárias para concluir a palavra.

O que eu quero é que os números aleatórios que eu estou gerando gerem letras necessárias com mais frequência, para que o jogador não precise passar o dia todo para completar uma palavra.

Eu tentei os seguintes métodos:

  1. Detecte todas as letras da palavra (a palavra tem sempre 6 letras), gere a matriz de índices de comprimento 6, atribua cada índice da matriz ao número aleatório da letra-2 à letra + 2 e, no final, escolha aleatoriamente um índice da matriz para mostrar.

  2. Tenha uma variável seletora cujo valor esteja no intervalo [0..2], gerado aleatoriamente, se o seletor == 0 detectar as letras que formam a palavra e escolher aleatoriamente uma letra, caso contrário, obterá aleatoriamente qualquer alfabeto de az.

Ambos os métodos não me forneceram nenhuma ajuda. Ficarei muito feliz se você puder me ajudar.

Obrigado por ler isso, espero que você tenha entendido a pergunta e estou aguardando a resposta.

Daniyal Azram
fonte
2
"ambos os métodos não me deram nenhuma ajuda" Por que não? O que não funcionou com esses métodos?
Vaillancourt
Não sei por que, mas ainda está demorando muito, como 1 minuto, para obter todos os alfabetos necessários.
Daniyal Azram
@DaniyalAzram, você provavelmente deve aumentar a frequência um pouco mais se essas letras não aparecerem com frequência suficiente, pois parece que esse é o seu problema.
JFA 14/06

Respostas:

21

Você realmente não quer uma distribuição aleatória. Aponto isso explicitamente, porque o que consideramos "aleatório" para o design geralmente não é uma aleatoriedade verdadeira.

Agora, com isso em mente, vamos adicionar alguns valores de ajustes - essas são coisas com as quais você mexerá até que o design pareça "certo".

ChooseLetter() {
    const float WordLetterProbability = 0.5f;
    if (Random.NextFloat01() < WordLetterProbability) {
        // Pick a random letter in the word
    }
    else {
        // Pick a random letter *not* in the word
    }
}

A probabilidade controla a probabilidade de uma determinada chamada para o ChooseLetter fornecer uma letra de uma palavra - em 0,5, você receberá uma letra de uma palavra aproximadamente todas as vezes. Em 0,25, um em cada quatro será uma letra do tipo etc.

Isso ainda é um pouco simplista - porque a aleatoriedade é, bem, aleatória , na verdade você não tem garantia de quanto tempo ficará entre as letras das palavras. (Em teoria, você pode ficar para sempre sem uma letra de palavra, é muito improvável.) Em vez disso, podemos adicionar um fator para aumentar a probabilidade de uma letra de palavra toda vez que não obtemos uma:

const float BaseWordLetterProbability = 0.5f;
const float WordLetterProbabilityIncrease = 0.25f;
float WordLetterProbability = BaseWordLetterProbability;
ChooseLetter() {
    if (Random.NextFloat01() < WordLetterProbability) {
        // Pick a random letter in the word
        WordLetterProbability = BaseWordLetterProbability;
    }
    else {
        // Pick a random letter *not* in the word
        WordLetterProbability += WordLetterProbabilityIncrease;
    }
}

Ok, agora agora nunca mais do que duas letras sem uma palavra. (Porque, após duas falhas, temos uma probabilidade de 1,0 de obter uma letra de palavra.)

Por fim, precisamos levar em consideração como funciona a escolha da letra em uma palavra. A fim de fornecer ao jogador as letras que eles realmente precisam, precisamos remover as letras do conjunto à medida que elas as receberem.

Por exemplo, se a palavra é "teste" e o jogador já possui 's', não queremos dar a ele outro 's', porque eles não precisam!

A partir daqui, o resto é aprimorado para se adequar ao seu design.

ACEfanatic02
fonte
Uau! como você chegou a isso?: p testarei seu método amanhã de manhã e acho que funcionará; fornecerei mais feedback assim que testar esse método. Muito obrigado por essa resposta.
Daniyal Azram
4
Estatisticas! Como designer de jogos ou programador, vale muito a pena aprender estatísticas . No mínimo, uma compreensão da probabilidade (e como combiná-las) é extremamente útil.
ACEfanatic02
1
Venha sugerir a mesma coisa. Ótima resposta. Lembre-se também de que uma solução como essa pode ser uma maneira de introduzir níveis de dificuldade. Fácil = 0,5, médio = 0,25, difícil = 0,10. etc
Tasos
Eu discordo que isso não é aleatório. Isso é aleatório, é apenas uma distribuição por partes, enquanto geralmente pensamos na distribuição uniforme. E, para acrescentar à sua ideia, eu iria além do que "Escolha uma letra aleatória da palavra", distribuiria pelas letras que ocorrem mais. Por exemplo, "mississippi" precisa de mais "s" e "i", então escolha mais deles.
Blaine
21

Você pode ponderar a probabilidade de todas as suas letras de acordo com a frequência com que elas ocorrem no idioma em que suas palavras estão. Uma boa orientação é o conjunto de scrabble . A versão em inglês, por exemplo, possui 12 E, mas apenas um Z e um Q.

Uma maneira simples de implementar isso é colocar todas as letras em uma sequência consecutiva, com cada letra aparecendo quantas vezes desejar e, em seguida, fazer com que o RNG pegue uma carta de uma posição aleatória. Exemplo de pseudocódigo:

const String letters = "AAAAAAAAABBCCDDDDEEEEEEEEEEEEFFGGGHHIIIIIIIIIJ/*...and so on...*/"

char randomLetter = letters[randomIntegerBetween(0, letters.length - 1)];
Philipp
fonte
2
+1 Esse é um bom conceito, mas suspeito que exista uma implementação mais elegante.
Evorlor
Para uma distribuição mais refinada, você pode armazenar uma tabela de frequências de letras dimensionadas para que elas somarem a 1, gerar um número aleatório de 0 a 1 e fazer um loop sobre a tabela subtraindo a frequência de cada letra do número aleatório até que torna-se zero ou negativo. Você pode até otimizar isso classificando as letras mais comuns primeiro na tabela. Ou use algo como o método de alias para evitar repetir completamente a tabela.
Ilmari Karonen
4
Isso não resolveria o problema. O problema é que o usuário precisa de letras específicas para terminar o jogo. Diga que eles precisam de um Z? O que então? Isso apenas tornaria as letras raras mais difíceis de tornar o usuário ainda mais frustrado.
AmazingDreams
O @AmazingDreams ressalta a coisa boa, mas podemos modificá-la um pouco, então atribuirei mais peso aos alfabetos necessários e menos peso aos outros. Devo dizer que este é um conceito muito bom a seguir.
Daniyal Azram
4

Aqui está uma maneira de aprimorá-lo usando um único parâmetro kque você pode ajustar.

Em vez de apenas escolher uma letra aleatória:

  1. escolha uma letra aleatória A
  2. escolha um número aleatório X
  3. se X > k e A não estiver [list of remaining needed letters], tente novamente em 1.

Quanto menor k, mais frequentemente a letra finalA será realmente necessária.

Para ajustar o algoritmo, jogue com qualquer valor para k, por exemplo k = 0.5 . Se você achar que o jogo é muito difícil, tente 0.4, etc., até encontrar um valor razoável. Isso também fornece diretamente uma configuração de dificuldade que , por exemplo, você pode querer aumentar à medida que o jogador avança no jogo.

sam hocevar
fonte
Obrigado pela resposta, mas esta parece ser a resposta do ACEfanatic02.
Daniyal Azram
3

Uma maneira simples de garantir que as letras necessárias apareçam em um determinado período é usar o preenchimento de uma matriz com as letras da palavra e o restante do alfabeto (talvez repetido) e, em seguida, embaralhar aleatoriamente a matriz (c ++ tem std :: random_shuffle na biblioteca padrão, se você estiver usando um idioma diferente, não será difícil de implementar).

Se você deseja que as letras da palavra apareçam mais rapidamente, coloque mais cópias das letras da palavra na matriz.

GuyRT
fonte
0

Se você estiver usando C ++, poderá usar uma distribuição já existente http://en.cppreference.com/w/cpp/numeric/random/piecewise_constant_distribution

Também amortizou a complexidade do tempo constante, melhor do que os métodos ingênuos. exemplo:

#include <iostream>
#include <string>
#include <map>
#include <random>
#include <numeric>

int main()
{
    constexpr double containedLetterWeight = 3.0;
    constexpr int iterations = 10000;
    std::string word = "DISTRIBUTION";

    std::mt19937 rng(123);

    std::vector<double> values('Z' - 'A' + 2);
    std::iota(values.begin(), values.end(), 0.0);

    std::vector<double> weights('Z' - 'A' + 1, 1.0);
    for(const auto& c : word) weights[c - 'A'] = containedLetterWeight;

    std::piecewise_constant_distribution<> dist(values.begin(), values.end(), weights.begin());

    std::map<char, int> results;
    for(int n = 0; n < iterations; ++n)
    {
        ++results[static_cast<char>(dist(rng)) + 'A'];
    }
    for(const auto& p : results)
    {
        std::cout << p.first << ' ' << static_cast<float>(p.second) / iterations << '\n';
    }
}
Sopel
fonte