ID:271042
 
I've been trying to think of how I would accomplish this, but I really can't figure out a way. Sometimes in my game, this code will pick the same person, over and over again. When someone becomes IT, I want their probability to go down. When someone isn't IT, their probability should go up. Is there a way to accomplish this?

proc/PickRandomPlayer()
if(!players.len) return
var/mob/M = pick(players) // Right here is where I want to make it pick a random player based on their probability.
world << "<font size=4><font color=#FF9900>[M] is IT!</font></font>"


Sorry if that isn't clear enough, but I tried. So ask questions if you need to.

-KirbyAllStar
I'm not sure on this matter, but this is how I would do it:

proc/PickRandomPlayer()
if(!players.len) return
for(var/mob/M in world)
if(prob(M.tagprob) && M in players)
world << "<font size=4><font color=#FF9900>[M] is IT!</font></font>"
//Do what you have to do here
break
else
continue


Watch out for bad indentations, I made the code right in this text box.
In response to RedlineM203
That should work. Note that your 'else' & 'continue' are obsolete.
Also, you can see he uses a 'players' list (which is IMO unneeded, but meh). You're looping threw all mobs in the world and checking if theyre in that list... instead, just loop threw all mobs in the 'players' list.
In response to Kaioken
Eh, there are still some things I need to learn.
In response to Kaioken
Well its not unneeded actually because players have the choice of whether to join or not. And I don't want it making a person on the login screen IT. But thanks for the help, I'll try it out in a few min.

-KirbyAllStar
Hmm.. I hope it works out. If that continue I added in the first place isn't for somehow repeating the loop until one is found, then there is a chance nobody will be picked at all.

I'm stressing myself out.
In response to Kaioken
Obsolete is the absolutely incorrect word for this situation. Redundant, pointless, needless, etc are correct, but not obsolete (redundant is probably pushing it, though).
In response to Popisfizzy
True. Don't mind me, haven't slept in a while. >_>
Gaming Asylum Productions wrote:
Well its not unneeded actually because players have the choice of whether to join or not. And I don't want it making a person on the login screen IT. But thanks for the help, I'll try it out in a few min.

You still don't have to use a list to do that; though if the list contains only players who, for example, ran the "join" verb, it's a good idea. But if so, it shouldn't really be named 'players'. >_>
In response to Kaioken
Kaioken wrote:
That should work.

Except for the fact that it won't. Doing if(prob(X)) in a loop like that is the wrong way to pick from a weighted list. The problem is that each time you use prob() it affects the chances of anything later. The effect is to bias the list toward picking the first one, and add a chance that no one will be picked at all.

Lummox JR
In response to RedlineM203
for(var/mob/M in players)
if(prob((M.tagLevel/players.len)*100))
world << "<font size=4><font color=#FF9900>[M] is IT!</font></font>"
M.it=1
M.gameStatus="IT"
break
else
continue


This is what I ended up using, the tagLevel is so that if a player becomes IT, their tagLevel will go down one, and if a player isn't IT, then their tagLevel will go up one. I haven't fully tested this, but so far it works. Thanks for all the help, not sure if the continue is required either, but I'm going to leave in their just in case.

-KirbyAllStar
In response to Lummox JR
Doing if(prob(X)) in a loop like that is the wrong way to pick from a weighted list.

Doing if(prob(X)) in a loop like that is the wrong way to pick from a weighted list.

I can't seem to think of a 'correct' way. The list contains mob references, and the probability is a /mob/var of each of those. How would you go about doing that properly?
In response to RedlineM203
RedlineM203 wrote:
I'm not sure on this matter, but this is how I would do it:

> proc/PickRandomPlayer()
> if(!players.len) return
> for(var/mob/M in world)
> if(prob(M.tagprob) && M in players)
> world << "<font size=4><font color=#FF9900>[M] is IT!</font></font>"
> //Do what you have to do here
> break
> else
> continue
>

Watch out for bad indentations, I made the code right in this text box.

This line is totally wrong:
if(prob(M.tagprob) && M in players)


There are two problems with it. The first is that you didn't put parentheses around M in players. This is important since the in operator has a very very low precedence. Of course, as was alredy pointed out, you'd be better off just looping through the players list in the first place.

The second problem is that calling prob() for each player is not the way to weight their chance of being picked. Try a little experiment: Flip a coin till you get heads, then stop. How many times did you flip it? Once? Twice? Chances are, you'll only have to flip it a small number of times, but there's even the chance that out of N tries, you'll never get heads at all. The problem here is, the probability for all the later flips changes based on the first. The chance of getting heads on the second flip is not 50%, but 25%, and on the third flip it's 12.5%, etc.

There is only one right way to pick from a weighted list: Add up the total weights, get a random number out of that total, and then count weights till you hit that number. I've already made this proc, pickweight():

proc/pickweight(list/L, defaultweight = 1)
var/tot = 0
var/item,weight
for(item in L)
weight = L[item]
if(isnull(weight))
L[item] = defaultweight
tot += defaultweight
else tot+=weight
tot *= rand()
for(item in L)
tot -= L[item]
if(tot < 0) return item


pickweight() takes an associative list. If you don't assign a probability it just goes with 1 by default. But you could say players[mob]=2 to give it 2 shares at being chosen. The numbers are arbitrary so everyone could be 100 by default if you want. A nice way to handle picking IT would be to keep a list of keys (in case someone logs out and comes back) for this.

var/list/ITpool = new
var/mob/IT

mob/Login()
... // other stuff here
if(!ITpool[key]) ITpool[key] = 1

proc/PickIT()
var/key
var/list/pool = new
for(var/client/C)
if(C.mob == IT) continue
pool[C.mob] = ITpool[C.key]
// don't use an empty list
if(!pool.len) return
IT = pickweight(pool)
// increase everyone's chances of being picked again
for(var/client/C) ++ITpool[C.key]
ITpool[IT.key] = 0 // reset IT to 0--no chance of being picked next round


If you want, you can actually bias the list further toward players who haven't been picked in a while by setting pool[C.mob]=ITpool[C.key]**2. Or you can use a smaller exponent like 1.5, which is probably better. With a 1.5 exponent, someone who has waited 2 turns to be IT is roughly 2.83 times as likely to be picked as someone who has only waited 1 turn. Someone who's waited 4 turns is 8 times as likely to be picked. Without an exponent, though, I think you're still likely to get a pretty good random-ish distribution.

Lummox JR
A 'continue' at the end of a loop is not needed. 'continue' is used to skip the current iteration and attempt to proceed to the next one, and this is automatically done anyway when the loop reaches the end. 'continue' is meant to be used for example in the beginning if a loop, to skip iterations if wanted; here is a proper use of it (though not particulary useful or helpful in this case).
proc/Get_Mobs()
. = list()
for(var/atom/A in world)
if(!ismob(A)) continue //if A isn't a mob, skip this iteration
. += A //add it to the list


The 'continue' isn't needed after your 'else' either, because in the if() the 'else' works with, you used break. Also, as implied above, regardless if the 'else' is true or not, when the loop reaches its end successfully, it will 'continue' to the next iteration.
This is similar to the way you don't need to use 'else' if you used 'return' in the if() it refers to.
In response to Kaioken
Kaioken wrote:
Doing if(prob(X)) in a loop like that is the wrong way to pick from a weighted list.

I can't seem to think of a 'correct' way. The list contains mob references, and the probability is a /mob/var of each of those. How would you go about doing that properly?

With pickweight(). I've posted the proc on BYONDscape, but it's actually listed here as well. The gist of it is that you need to take a total of your weights and then grab from a random point in there.

Lummox JR
That code isn't going to work for you because it doesn't handle the probabilities correctly. Redline didn't know the right math to make it work.

Lummox JR
In response to RedlineM203
RedlineM203 wrote:
Hmm.. I hope it works out. If that continue I added in the first place isn't for somehow repeating the loop until one is found, then there is a chance nobody will be picked at all.

The continue statement does not mean "Keep looping until I tell you to break." (Even then, the loop would still be wrong because of the way prob() is being used.) All continue does is say: "Stop doing the loop for this item, and move to the next one." So the statement has no effect where you put it.

Lummox JR