Formula de aparición de objetos

AikonCWD

Espero que me lleve menos tiempo que Cursed Gem. Al menos el juego es bastante básico y no pretendo llevarlo hasta el extremo (publicarlo en Steam, monetizarlo, etc...) lo hago más para practicar y aprender a programar cosas diferentes.

Por cierto, tengo una duda que en breves tendré que solventar y prefiero preguntaros a ver como lo implementaríais vosotros:

ObjetoDescripciónAparición
Arma9
Armadura9
Comida17
Poción26
Pergamino27
Anillo6
Varita6

Imaginamos que tengo esta tabla de objetos. El número de "aparición" indica la probabilidad de que aparezca ese objeto. Si sumamos todos los números da 100.
De esta forma, si genero 100 objetos, el 26% de ellos deberían ser pociones. Mi duda es: cual es la forma más sencilla de implementar este sistema, y que me permita luego tunear los valores de forma rápida y sencilla.

Lo primero que se me ha ocurrido es representar esas probabilidad es una barra del 1 al 100. De tal forma que si al generar un número random, determine el tipo de item generado.

  • Si se genera un número del 1 al 9, entonces sale un arma.
  • Si se genera un número del 10 al 18, entonces sale una armadura.
  • Si se genera un número del 19 al 36, entonces sale una ración de comida
  • etc...

Pero este sistema no lo veo ni óptimo, ni cómodo. Primero porque en la implementación por código no aparecen los % reales (9, 9, 17) si no que aparece una suma de estos. Y eso a la larga me confunde y me es complicado modificarlos a mano para balancear el juego a posteriori.

A alguien se le ocurre un método mejor? No se si me he explicado muy bien xd.

n3krO

https://www.mediavida.com/foro/dev/funcion-devuelva-numero-aleatorios-655780

Ahi tienes el hilo.

Y el codigo que mas te gustó:

const porcentajes = [.5, .3, .2];
const elementos = ["el 1", "el 2", "el 3"];

const cantidad = porcentajes.length - 1;

const random = Math.random();

for (let i = 0, j = 0; i < cantidad; i++) {

j += porcentajes[i];

if (random < j) { return elementos[i]; }

}

return elementos[cantidad];
HeXaN

La respuesta correcta de ese hilo es la de @Fyn4r .

1 respuesta
n3krO

#3 La de fyn4r no explica ninguna implementacion escalable

1 respuesta
HeXaN

#4 Imagina pensar e implementarlo tú :O

1 respuesta
AikonCWD

Pero esa explicación era para distribuir los items de forma uniforme por el mapa, no? para que no se amontonen de forma random en un lugar concreto. Ese handicap no lo tengo.

2 respuestas
n3krO

#6 Es lo mismo Aikon.

#5 Imagina que en el mismo hilo ya tienes la respuesta escalable y todo :psyduck:

2 respuestas
Fyn4r

#6 en realidad (desde el punto de vista matematico) este problema es exactamente el mismo que el anterior, así que te valen las mismas soluciones.

#7 no se que esta usando, pero todos los lenguajes que utilizo que tienen utilidades para números aleatorios ya tienen estas cosas implementadas xd

AikonCWD

#7 Pues no estoy entendiendo como eso resuelve el problema. Voy a pensar un rato

1 respuesta
n3krO

#9 Calcula cada probabilidad.

Empieza con el array de pesos. Calcula sumatorio. Calcula el array de porcentajes haciendo peso/sumatorio para cada elemento. Y despues ya estas en el mismo caso. Un array de porcentajes y un numero aleatorio entre 0 y 1.

Hipnos

Esto normalmente se resuelve con un array de pesos estáticos.

Pera 100
Manzana 130
Uva 100
Manzana dorada 10

Puedes añadir todos los que quieras. Luego sumas todos los pesos y la probabilidad de salir es peso/(total de pesos).

3
AikonCWD

Vale, al final ya lo tengo implementado como quería. He tirado más hacia lo que ha propuesto @Hipnos (usando un diccionario en lugar de array) independientemente de que lo de @Fyn4r sea lo más óptimo.

El problema de @Fyn4r es que no estoy entendiendo como su solución resuelve el problema.

Recordemos que yo quiero trabajar con los valores naturales de la tabla, que me sea fácil cambiarlos y que la opción escale bien. Al final me ha quedado así:

Y el resultado de generar 15 items:

n = 75[pergamino]
n = 15[armadura]
n = 54[pocion]
n = 53[pocion]
n = 21[comida]
n = 76[pergamino]
n = 62[pergamino]
n = 69[pergamino]
n = 38[pocion]
n = 33[comida]
n = 98[varita]
n = 53[pocion]
n = 30[comida]
n = 12[armadura]
n = 87[pergamino]

Ahora como veo que salen muchos pergaminos y pociones, puedo bajar su número de forma fácil y sin pensar en %, etc... Gracias chicos :)

4 respuestas
Hipnos

#12 Como consejo yo pondría el valor 100 para un objeto de referencia que sea relativamente habitual, a veces es difícil calibrar la frecuencia.

1 respuesta
AikonCWD

#13 Por suerte no tengo que romperme mucho el coco calculando y balanceando el juego. Ya que me estoy basando en el source code de un juego de 1980 y ahí estoy viendo los valores y % que usaron en su día (y que funcionan bastante bien).

carra
#12AikonCWD:

Ahora como veo que salen muchos pergaminos y pociones, puedo bajar su número de forma fácil y sin pensar en %, etc... Gracias chicos

No te fies de eso tras crear una cantidad tan pequeña, la varianza puede engañar

1 1 respuesta
AikonCWD

#15 Correcto. He simulado 100 items y los he añadido a sus categorías, el resultado de la primera tirada ha sido:

arma=13
armadura=9
comida=16
pocion=25
pergamino=23
anillo=6
varita=8

El resultado de la segunda tirada:

arma=10
armadura=13
comida=19
pocion=31
pergamino=20
anillo=5
varita=2

Completamente pareja a los % acordados en el diccionario y suficientemente random para que sea divertido. Teniendo en cuenta que el juego consta de 26 niveles y el promedio es de 5,5 items por nivel (143 items en total), nos saldría una tirada como esta:

arma=13
armadura=14
comida=23
pocion=30
pergamino=43
anillo=9
varita=11

Cada comida te permite aguantar 1500 turnos, así que 1500 * 23 es suficiente para completar la aventura. 13 armas y 14 armaduras son más que suficientes y permite que el jugador encuentre un arma de cada tipo aprox. El resto de items están equilibrados ya que se usan muchas pociones y pergaminos. Con tiradas grandes el reparto queda ajustado y equilibrado

1
Buffoncete

#12 lo único que yo cambiaría es que el módulo, en vez de hacerlo a 100 lo haría a la suma de los valores de tus items y me quitaría magic numbers.

Ahora mismo tus items suman 100, con lo cuál ese magic number te funciona, si quisieras añadir

"hard_currency":2

para darle muy poco peso, al inicializar el juego sólo tendrías que sumar todos los pesos, con este nuevo añadido sería 102 y la primera linea de get_item sería

randi() % suma_pesos

.

1 respuesta
EruGreen

#12 no soy precisamente un experto programando, pero forzar que la suma de las probabilidades de 100 para mi te resta versatilidad, por ejemplo quizas quieres que haya una zona que no caigan pociones o una en la que ademas de lo que mencionas tambien caigan "cristales", tambien te permitiria que en una zona cayesen el doble de "armas" sin tener que modificar las otras probabilidades

1 respuesta
AikonCWD

#18 Si tuviera que contemplar esas cosas, buscaría otro método. Pero para el juego que estoy haciendo necesito que las probabilidades sean esas.
#17 Sí, pero me gusta tener el listado de items redondeados a 100. Me es mucho más fácil y natural para pensar los valores y balancearlos.

3 respuestas
carra
#19AikonCWD:

Sí, pero me gusta tener el listado de items redondeados a 100. Me es mucho más fácil y natural para pensar los valores y balancearlos.

Esa manera es bastante intuitiva, pero existe otra alternativa. Pongo un ejemplo fácil que todos conocemos: Tetris. La manera en que determina el juego las siguientes piezas ha cambiado en los tetris modernos.

Los antiguos hacían lo que está haciendo Aikon: asignan una probabilidad a cada pieza (la misma para todas), y van creando una secuencia verdaderamente aleatoria. Esto tiene una desventaja: puede haber momentos donde por ejemplo caigan muchos cuadrados, y casi ninguna barra. Esto se compensaría luego en otros momentos. Eso es normal. Si tiráis una moneda, algunas veces os saldrán varias caras seguidas.

Lo que hacen los tetris modernos es generar las piezas de 7 en 7. Se genera una pieza de cada y se va variando sólo el orden. Así garantizan que nunca puede haber sequías demasiado largas de ninguna pieza. En el caso de Aikon, podría tener un grupo de items predeterminado (por ejemplo: 3 pociones, 2 comidas, 1 anillo, etc) y generar cada vez ese mismo grupo con distinto orden. Las proporciones se mantienen y nunca te quedarás mucho rato sin comida por ejemplo.

1
Buffoncete

#19 si quieres que el sistema redondee a 100 entonces añadiría, primero la variable como global y no como magic number, y segundo un check para que la suma de siempre 100 que sale al ejecutar los tests.

Czhincksx

#19 Eso es muy problemático. El día que quieras meter un nuevo ítem te altera los valores de todo lo demás.

La fórmula que se suele usar es:

Sumar todos los valores. 
Tirar un valor = random (0, Sumatotal).
int sum = 0;
for(int i = 0; i < numItemsDistintos; i++){
  sum += item[i];
 if(valor < sum){
    tipoAleatorio = i;
    break;
}
}

Esto te da facilidad para por ejemplo, duplicar las probabilidades de que salga un tipo de ítem de forma temporal. Imaginemos por ejemplo que una armadura te da +30% de posibilidades de drop de espada. No tendrías que comerte la cabeza con lo del 100%. Si luego a nivel mental quieres llevar la cuenta de qué porcentaje es cada cosa, puedes hacerlo con un excel. O puedes ponerlos a mano así hasta los 100, pero usando este método de cálculo.

JuAn4k4

Nunca querrás sacar más de 1 item?

Usuarios habituales

  • Buffoncete
  • carra
  • AikonCWD
  • Hipnos
  • n3krO
  • Fyn4r
  • HeXaN