Sto provando a escogitare un modo per creare un "loot table" per un RPG, soprattutto per vedere se è possibile farlo.
Sono in parte ispirato alle vecchie tabelle di stile di D & D, che elencerebbero un intervallo di valori per ogni oggetto sul tavolo (ad esempio, 0-20 tempo è chiaro, 21-50 tempo sta piovendo, 51-99 è tempo nuvoloso) e arrotolerai un dado per vedere dove cadi sul tavolo.
Mi piacerebbe farlo senza un sacco di If / Elses, perché potrei voler costruire tabelle da dati memorizzati in un file o database.
Quindi sono andato ponderando i valori sul "tavolo". Pesi più alti significherebbe una maggiore possibilità di selezionare quel valore. Sommare tutti i pesi e quindi dividere ogni singolo peso in base alla somma per avere la possibilità di passare. Se i pesi sono 1, 1, 2 e 3, il peso di 3 dovrebbe avere circa 3/7 possibilità di essere selezionato.
QUINDI ecco cosa mi è venuto in mente:
public class WeightedRandomGroup<T> {
private Random dice = new Random();
private List<WeightedValue<T>> weightedList = new ArrayList<>();
private float weightSum = 0;
boolean isSorted = false;
public void Insert(WeightedValue<T> wv){
weightSum += wv.Weight();
weightedList.add(wv);
isSorted = false;
}
public T Roll(){
if(weightSum <= 0.001){
return null;
}
if(!isSorted){
Sort();
}
T val = null;
while(val == null)
{
for(WeightedValue<T> wv : weightedList)
{
//System.out.println("Weight: " + wv.Weight());
if(dice.nextFloat() <= (wv.Weight() / weightSum)){
val = wv.Value();
break;
}
}
}
return val;
}
private void Sort(){
Collections.sort(weightedList, comparator);
isSorted = true;
}
private Comparator<WeightedValue<T>> comparator = new Comparator<WeightedValue<T>>(){
public int compare(WeightedValue<T> w1, WeightedValue<T> w2){
if(w1.Weight() > w2.Weight())
return 1;
else if (w1.Weight() == w2.Weight())
return 0;
else
return -1;
}
};
}
E WeightedValue
public class WeightedValue<T> {
private T value;
private float weight;
public WeightedValue(int weight, T value){
this.weight = weight;
this.value = value;
}
public float Weight() { return weight; }
public T Value() { return value; }
}
La procedura consiste nel passare attraverso ogni peso e "rollare" per vedere se riusciamo a passare sotto la sua probabilità di passare. La lista è ordinata, quindi iniziamo con i pesi più bassi e saliamo ai massimi pesi e continuiamo a scorrere l'elenco fino a quando non passiamo sotto uno degli elementi.
Quelli che hanno fatto matematica discreta o simili probabilmente vedranno un problema, che mostrerò ora:
Dato un tavolo in cui ogni valore ha un peso uguale, questo è il numero di volte in cui ogni articolo viene selezionato quando rotola (su 10.000 rotoli):
- Articolo 1: 2534
- Articolo 2: 2078
- Articolo 3: 1774
- Articolo 4: 1436
- Elemento 5: 1188
- Articolo 6: 990
Se tutto ha lo stesso peso, allora tutto dovrebbe avere un numero abbastanza vicino di colpi, ma chiaramente non è questo il caso con l'Articolo 6 che viene arrotolato meno della metà delle volte che si tratta dell'Articolo 1 o dell'Articolo 2!
Il che ha un senso. Se abbiamo 4 oggetti con pari opportunità, quindi usando il mio codice sopra abbiamo una probabilità del 25% per il primo oggetto. Se fallisce, tiriamo per 2, poi se fallisce 3, e così via. Questo significa che abbiamo una probabilità del 75% di provare a tirare per il secondo oggetto. una probabilità del ~ 56% di rotolare per il terzo oggetto e una probabilità del 42% di rotolare per il 4 ° oggetto. (se ho fatto bene la matematica)
Pubblicherò ciò che mi è venuto in mente per risolvere il problema, ma mi interessa sapere se c'è un modo migliore per "tirare un dado" su un tavolo.