Como evito "muito" riscos de sorte / azar na geração de números aleatórios?

30

Atualmente, estou lidando com um sistema de combate multiplayer em que o dano causado pelos jogadores é sempre multiplicado por um fator aleatório entre 0,8 e 1,2.

Em teoria, um RNG verdadeiramente aleatório pode eventualmente render o mesmo número muitas vezes (veja o dilema de Tetris ). Isso pode resultar em uma partida em que o jogador está sempre causando dano muito alto, enquanto o outro sempre causa dano muito baixo.

O que posso fazer para garantir que isso não aconteça? Alguns RNGs são melhores que outros em evitar repetições?

Usuário não encontrado
fonte
Não vejo como isso funciona. Claro que você terá uma sequência de x1, x2, x3, x4 .. onde todos os x são grandes. Isso não é aleatório?
The Duck Comunista

Respostas:

26

Você pode resolvê-lo da mesma maneira que o Tetris, fazendo uma lista predefinida dos resultados dos danos e embaralhando.

Digamos que você saiba que o jogador causará 0,8x a 1,2x de dano com uma distribuição linear. Veja a lista [0.8, 0.9, 1.0, 1.1, 1.2]. Embaralhe aleatoriamente , para obter, por exemplo, [1.2, 1.0, 0.8, 0.9, 1.1].

A primeira vez que o jogador causa dano, ele causa 1.2x. Então 1x. Então, etc, para 1.1x. Somente quando a matriz estiver vazia , você deverá gerar e embaralhar uma nova matriz.

Na prática, você provavelmente desejará fazer isso com mais de 4 matrizes de uma só vez (por exemplo, comece com [0.8,0.8,0.8,0.8,0.9,0.9,0.9,0.9, ...]). Caso contrário, o período da sequência é baixo o suficiente para que os jogadores possam descobrir se o próximo golpe é "bom" ou não. (Embora isso também possa adicionar mais estratégia ao combate, como na tabela Hoimi de Dragon Quest IX , que as pessoas descobriram como investigar observando os números de cura e ajustando até garantir uma queda rara.)


fonte
3
Para torná-lo um pouco mais aleatório, você sempre pode ter metade da lista como números aleatórios e a outra metade calculada como (2-x) para obter a média correta.
Adam
2
@ Adam: Esse método realmente só funciona para este exemplo em particular; se você está distribuindo peças de Tetris ao invés de multiplicadores de dano, o que é o bloco 2-S?
6
O termo usual para isso é uma espécie de sistema "aleatório sem substituição". É apenas análogo a usar um baralho de cartas em vez de dados, na verdade.
Kylotan
Melhor ainda, você poderia fazer metade dos números realmente aleatórios, e apenas metade deles estará sujeita a esta regra.
o0 '.
11
Ainda pode resultar na distribuição local que não se assemelha à distribuição global, que é exatamente o que a pergunta não deseja. Termos como "realmente aleatório" são pseudomatemática vaga; quanto mais você definir quais propriedades estatísticas deseja, mais clara será sua intenção e design do jogo.
5

Na verdade, eu escrevi algum código para fazer isso . A essência disso é usar estatísticas para corrigir riscos infelizes. A maneira de fazer isso é acompanhar quantas vezes o evento ocorreu e usá-lo para influenciar o número gerado pelo PRNG.

Em primeiro lugar, como acompanhamos a porcentagem de eventos? A maneira ingênua de fazer isso seria manter todos os números já gerados na memória e calculá-los em média: o que funcionaria, mas é terrivelmente ineficiente. Depois de pensar um pouco, criei o seguinte (que é basicamente uma média móvel acumulada ).

Pegue as seguintes amostras de PRNG (onde procuramos se a amostra for> = 0,5):

Values: 0.1, 0.5, 0.9, 0.4, 0.8
Events: 0  , 1  , 1  , 0  , 1
Percentage: 60%

Observe que cada valor contribui para 1/5 do resultado final. Vejamos de outra maneira:

Values: 0.1, 0.5
Events: 0  , 1

Observe que 0contribui com 50% do valor e 1contribui com 50% do valor. Levado um pouco mais longe:

Values: [0.1, 0.5], 0.9
Events: [0  , 1  ], 1

Agora, os primeiros valores contribuem com 66% do valor e os últimos 33%. Podemos basicamente destilar isso até o seguinte processo:

result = // 0 or 1 depending on the result of the event that was just generated
new_samples = samples + 1

average = (average * samples / new_samples) + (result * 1 / new_samples)
// Essentially:
average = (average * samples / new_samples) + (result / new_samples)

// You might want to limit this to, say, 100.
// Leaving it to carry on increasing can lead to unfairness
// if the game draws on forever.
samples = new_samples

Agora precisamos polarizar o resultado do valor amostrado no PRNG, porque estamos buscando uma porcentagem de chance aqui, as coisas são muito mais fáceis (em comparação, digamos, quantidades aleatórias de dano em um RTS). Isso vai ser difícil de explicar, porque "apenas me ocorreu". Se a média for menor, significa que precisamos aumentar a chance do evento ocorrer e vice-versa. Então, alguns exemplos

average = 0.1
desired = 0.5
corrected_chance = 83%

average = 0.2
desired = 0.5
corrected_chance = 71%

average = 0.5
desired = 0.5
corrected_change = 50%

Agora, o que 'me ocorreu' é que, no primeiro exemplo, 83% eram apenas "0,5 de 0,6" (em outras palavras, "0,5 de 0,5 mais 0,1"). Em termos de eventos aleatórios, isso significa:

procced = (sample * 0.6) > 0.1
// or
procced = (sample * 0.6) <= 0.5

Portanto, para gerar um evento, você basicamente usaria o seguinte código:

total = average + desired
sample = rng_sample() * total // where the RNG provides a value between 0 and 1
procced = sample <= desired

E, portanto, você recebe o código que eu coloquei na essência. Tenho certeza de que tudo isso pode ser usado no cenário de caso de dano aleatório, mas não tomei tempo para descobrir isso.

Isenção de responsabilidade: estas são todas as estatísticas caseiras, não tenho formação em campo. Meus testes de unidade passam embora.

Jonathan Dickinson
fonte
Parece um erro no seu primeiro exemplo, porque um valor de 0,1 e 0,9 resulta em um evento 0. Mas você está basicamente descrevendo como manter uma média móvel cumulativa ( en.wikipedia.org/wiki/Moving_average#Cumulative_moving_average ) e corrigindo com base nisso. Um risco é que cada resultado seja significativamente inversamente correlacionado com o resultado anterior, embora essa correlação diminua com o tempo.
Kylotan
11
Eu ficaria tentado a alterar isso para usar um sistema 'integrador com vazamento': comece com a média inicializada em 0,5 e, em vez de contar amostras, escolha um valor constante arbitrário (por exemplo, 10, 20, 50 ou 100) que não é incrementado . Então, pelo menos, a correlação entre 2 valores subsequentes é constante durante o uso do gerador. Você também pode ajustar o valor constante - valores maiores significam correção mais lenta e aleatoriedade mais aparente.
Kylotan
@Kylotan obrigado, obrigado por fornecer o nome. Não sei exatamente o que você quer dizer com o seu segundo comentário - talvez dê uma nova resposta?
11136 Jonathan Dickinson
Isso é bastante inteligente e não tem as limitações de matrizes. Entendo a sugestão de Kylotan, que é inicializar samplesem seu valor máximo (neste caso, 100) desde o início. Dessa forma, não são necessárias 99 iterações para o RNG se estabilizar. De qualquer forma, a única desvantagem que posso ver com esse método é que ele não garante justiça, simplesmente garante uma média constante.
Usuário não encontrado
@ jSepia - na verdade, você ainda receberia execuções de justiça / injustiça, mas elas seriam seguidas (geralmente) por uma execução equilibrada. Por exemplo, no meu teste de unidade, forcei 100 não-procs e recebi ~ 60 procs quando fiz as amostras reais. Em situações não influenciadas (se você olhar para o código), um processo de 50% geralmente vê, na pior das hipóteses, execuções de 2/3 em qualquer direção. Mas um jogador pode ter uma corrida que lhes permite derrotar o outro jogador. Se você quiser viés que mais fortemente a feira: total = (average / 2) + desired.
11136 Jonathan Dickinson
3

O que você está pedindo é, na verdade, o oposto da maioria dos PRNGs, uma distribuição não linear. Basta colocar algum tipo de lógica de retorno decrescente em suas regras, supondo que tudo acima de 1,0x seja algum tipo de "golpe crítico", apenas diga que a cada rodada suas chances de conseguir um crítico aumentam por X, até que você consiga um em Nesse ponto, eles são redefinidos para Y. Em seguida, você faz duas jogadas a cada rodada, uma para determinar a crítica ou não, e outra para a magnitude real.

coderanger
fonte
11
Esta é a abordagem geral que eu adotaria: você usa a distribuição uniforme do RNG, mas o transforma. Você também pode usar a saída do RNG como uma entrada para sua própria distribuição personalizada que se reajusta com base no histórico recente, ou seja, para forçar a variação nas saídas, para que pareça "mais aleatório" em termos de percepção humana.
Michael
3
Na verdade, eu conheço um MMO que faz algo assim, mas a chance de um crítico realmente aumenta cada vez que você recebe um até não conseguir um, e ele redefine para um valor muito baixo. Isso leva a raros rachaduras que são muito satisfatórias para o jogador.
Coderanger 17/04/11
Soa como um bom alg, períodos longos e secos sempre foram frustrantes, mas não levam a estrias malucas.
Michael
2
Corrigir isso não requer uma distribuição não linear, apenas requer que subconjuntos sequenciais de tempo curtos da distribuição tenham as mesmas propriedades que a própria distribuição.
é assim que os jogos da Blizzard fazem isso, pelo menos desde Warcraft 3
dreta
2

Sid Meier fez um excelente discurso no GDC 2010 sobre esse tópico e os jogos do Civilization. Vou tentar encontrar e colar o link mais tarde. Em essência - aleatoriedade percebida não é a mesma coisa que aleatoriedade verdadeira. Para fazer as coisas parecerem justas, você precisa analisar os resultados anteriores e prestar atenção à psicologia dos jogadores.

Evite riscos de má sorte a todo custo (se os dois turnos anteriores tiverem azar, o próximo deve ter a sorte). O jogador deve ter sempre mais sorte do que o adversário da IA.

Kromster diz apoio Monica
fonte
0

Use um viés de mudança

01r. Defina inicialmente um valor de viés,b, para 0 0.

A distribuição geral será influenciada pela seguinte fórmula:

rexp(-b)

O efeito aqui é que quando b for positivo, o número resultante será inclinado para 1 1. Quandob for negativo, o número resultante será inclinado para 0 0.

Pegue esse número e dimensione-o adequadamente para o intervalo desejado.

Cada vez que um jogador rola favoravelmente, subtraia o viés. Cada vez que o jogador rola desfavoravelmente, aumenta o viés. A quantia alterada pode ser dimensionada de acordo com a (des) favorável que a rolagem é ou pode ser uma quantia fixa (ou uma combinação). Você precisará ajustar valores específicos para se adequar à sensação que deseja.

Beefster
fonte