Estou tentando fazer uma espécie de jogo em que tenho uma grade de 20x20 e mostro um jogador (P), um alvo (T) e três inimigos (X). Todos estes têm uma coordenada X e Y que são atribuídos usando rand()
. O problema é que, se eu tentar obter mais pontos no jogo (recargas de energia, etc.), eles se sobrepõem a um ou mais dos outros pontos porque o alcance é pequeno (de 1 a 20, inclusive).
Estas são minhas variáveis e como estou atribuindo valores a elas: ( COORD
é um struct
com apenas um X e um Y)
const int gridSize = 20;
COORD player;
COORD target;
COORD enemy1;
COORD enemy2;
COORD enemy3;
//generate player
srand ( time ( NULL ) );
spawn(&player);
//generate target
spawn(&target);
//generate enemies
spawn(&enemy1);
spawn(&enemy2);
spawn(&enemy3);
void spawn(COORD *point)
{
//allot X and Y coordinate to a point
point->X = randNum();
point->Y = randNum();
}
int randNum()
{
//generate a random number between 1 and gridSize
return (rand() % gridSize) + 1;
}
Quero adicionar mais coisas ao jogo, mas a probabilidade de sobreposição aumenta quando faço isso. Existe alguma maneira de corrigir isso?
rand()
é um RNG lamentável e, de qualquer maneira, com um alcance tão pequeno, você não precisa apenas esperar colisões, elas são quase garantidas.rand()
é um péssimo RNG, provavelmente é apropriado para um jogo para um jogador, e a qualidade do RNG não é o problema aqui.rand()
parece ser irrelevante aqui. Não há criptografia envolvida, e qualquer RNG provavelmente dará colisões em um mapa tão pequeno.Respostas:
Enquanto os usuários que reclamam
rand()
e recomendam melhores RNGs estão certos sobre a qualidade dos números aleatórios, eles também estão perdendo o quadro geral. Duplicatas em fluxos de números aleatórios não podem ser evitadas, elas são um fato da vida. Esta é a lição do problema do aniversário .Em uma grade de 20 * 20 = 400 posições possíveis de spawn, é esperado um ponto duplicado de spawn (probabilidade de 50%), mesmo ao gerar apenas 24 entidades. Com 50 entidades (ainda apenas 12,5% de toda a grade), a probabilidade de uma duplicata é superior a 95%. Você tem que lidar com colisões.
Às vezes, você pode desenhar todas as amostras de uma só vez e usar um algoritmo de reprodução aleatória para desenhar
n
itens distintos garantidos. Você só precisa gerar a lista de todas as possibilidades. Se a lista completa de possibilidades for muito grande para armazenar, você poderá gerar posições de reprodução uma de cada vez, como faz agora (apenas com um RNG melhor) e simplesmente gerar novamente quando ocorrer uma colisão. Embora seja provável haver algumas colisões, muitas colisões consecutivas são exponencialmente improváveis, mesmo que a maior parte da grade seja preenchida.fonte
Se você sempre deseja evitar reproduzir uma nova entidade em um local que já foi alocado para outra coisa, você pode mudar um pouco o processo. Isso garantiria locais únicos, mas requer um pouco mais de sobrecarga. Aqui estão os passos:
Desde que você esteja removendo o local do conjunto do qual está escolhendo, não haverá chance de uma segunda entidade receber o mesmo local (a menos que esteja escolhendo os locais em mais de um segmento de uma vez).
Um análogo do mundo real a isso seria tirar uma carta de um baralho de cartas. Atualmente, você está embaralhando o baralho, comprando uma carta e marcando-a, colocando a carta comprada de volta no baralho, re-embaralhando e comprando novamente. A abordagem acima pula a colocação do cartão de volta no baralho.
fonte
Pertencente a
rand() % n
ser inferior ao idealDoing
rand() % n
tem uma distribuição não uniforme. Você receberá um número desproporcional de certos valores porque o número de valores não é múltiplo de 20Em seguida,
rand()
normalmente é um gerador congruencial linear (existem muitos outros , apenas esse é o mais provável implementado - e com parâmetros abaixo do ideal (existem várias maneiras de selecionar os parâmetros)). O maior problema com isso é que geralmente os bits baixos (os que você obtém com uma% 20
expressão de tipo) não são tão aleatórios. Lembro-me de umrand()
de anos atrás, onde o bit mais baixo alternava de1
para0
cada chamada pararand()
- não era muito aleatório.Na página do manual rand (3):
Agora isso pode ser relegado à história, mas é bem possível que você ainda tenha uma implementação ruim do rand () oculta em algum lugar da pilha. Nesse caso, ainda é bastante aplicável.
A coisa a fazer é realmente usar uma boa biblioteca de números aleatórios (que fornece bons números aleatórios) e depois pedir números aleatórios dentro do intervalo desejado.
Um exemplo de um bom número de código aleatório (a partir das 13:00 no vídeo vinculado)
Compare isso com:
Execute esses dois programas e compare com que frequência determinados números aparecem (ou não aparecem) nessa saída.
Vídeo relacionado: rand () considerado prejudicial
Alguns aspectos históricos do rand () causando bugs no Nethack que você deve observar e considerar em suas próprias implementações:
Problema do Nethack RNG
Enquanto o anterior foi de 2003, ainda deve ser lembrado, pois pode não ser o caso de todos os sistemas que executam o jogo pretendido serem um sistema Linux atualizado com uma boa função rand ().
Se você está fazendo isso sozinho, pode testar o quão bom é o seu gerador de números aleatórios escrevendo algum código e testando a saída com ent .
Sobre as propriedades de números aleatórios
Existem outras interpretações de 'aleatório' que não são exatamente aleatórias. Em um fluxo aleatório de dados, é bem possível obter o mesmo número duas vezes. Se você jogar uma moeda (aleatória), é bem possível obter duas caras seguidas. Ou jogue um dado duas vezes e obtenha o mesmo número duas vezes seguidas. Ou girando uma roleta e obtendo o mesmo número duas vezes lá.
A distribuição de números
Ao reproduzir uma lista de músicas, as pessoas esperam que 'aleatório' signifique que a mesma música ou artista não será tocado pela segunda vez consecutiva. Jogar uma lista de reprodução The Beatles duas vezes seguidas é considerado 'não aleatório' (embora seja aleatório). A percepção de que para uma lista de reprodução de quatro músicas tocou um total de oito vezes:
é mais 'aleatório' do que:
Mais sobre isso para o 'embaralhar' de músicas: Como embaralhar as músicas?
Em valores repetidos
Se você não deseja repetir valores, há uma abordagem diferente que deve ser considerada. Gere todos os valores possíveis e embaralhe-os.
Se você está ligando
rand()
(ou qualquer outro gerador de números aleatórios), está ligando para substituição. Você sempre pode obter o mesmo número duas vezes. Uma opção é descartar os valores repetidamente até selecionar um que atenda aos seus requisitos. Vou salientar que isso tem um tempo de execução não-determinístico e é possível que você se encontre em uma situação em que há um loop infinito, a menos que comece a fazer um rastreio mais complexo.Lista e Escolha
Outra opção é gerar uma lista de todos os possíveis estados válidos e, em seguida, selecionar um elemento aleatório nessa lista. Encontre todos os pontos vazios (que atendem a algumas regras) na sala e escolha um aleatório nessa lista. E depois faça isso repetidamente até terminar.
Aleatório
A outra abordagem é embaralhar como se fosse um baralho de cartas. Comece com todos os pontos vazios da sala e comece a atribuí-los, distribuindo os pontos vazios, um de cada vez, para cada regra / processo solicitando um ponto vazio. Você termina quando fica sem cartas ou as coisas param de pedir por elas.
fonte
Next, rand() is typically a linear congruential generator
Isso não é verdade em muitas plataformas agora. Na página do manual rand (3) do linux: "As versões do rand () e srand () na Biblioteca C do Linux usam o mesmo gerador de números aleatórios que random (3) e srandom (3), portanto, os bits de ordem inferior deve ser tão aleatório quanto os bits de ordem superior ". Além disso, como aponta @delnan, a qualidade do PRNG não é o verdadeiro problema aqui.RAND_MAX
32767, a diferença é 1638 maneiras possíveis de obter alguns números vs 1639 para outros. Parece improvável que faça muita diferença prática no OP.A solução mais simples para esse problema foi citada nas respostas anteriores: é fazer uma lista de valores aleatórios ao lado de cada uma das suas 400 células e, em seguida, classificar essa lista aleatória. Sua lista de células será classificada como a lista aleatória e, dessa forma, será embaralhada.
Este método tem a vantagem de evitar totalmente a sobreposição de células selecionadas aleatoriamente.
A desvantagem é que você precisa calcular um valor aleatório em uma lista separada para cada uma de suas células. Então, você prefere não fazê-lo enquanto o jogo começa.
Aqui está um exemplo de como você pode fazer isso:
Resultado:
Basta alterar NUMBER_OF_SPAWNS para obter células mais ou menos aleatórias, isso não altera o tempo de computação necessário para a tarefa.
fonte