"""
A little simulation for why it is that the higher in the hierarchy
one is, the more likely they're rogues.
Blog post on this (in German, sorry):
https://blog.tfiu.de/fast-alles-schurken.html
"""
import random
# the probability a rogue wins over an angel
ROGUE_ADVANTAGE = 0.66
_WIN_PROB = {
# this gives the probablities of winning a competition between
# rogues and angles. The key is a pair of booleans saying which
# player behaves like a rogue, the value is the probability
# that the first player wins.
#
# Basically, 1 and 2 have equal chances of winning if both
# are angels or both are rogues, but rogue vs. angel wins with
# ROGUE_ADVANAGE.
(False, False): 0.5,
(False, True): 1-ROGUE_ADVANTAGE,
(True, False): ROGUE_ADVANTAGE,
(True, True): 0.5,}
class Actor:
"""A model for an actor in our game.
It is characterised by its angelicity, the probablility for acting
non-roguely.
"""
def __init__(self, angelicity):
self.angelicity = angelicity
def is_rogue(self):
"""returns true if the actor acts like a rogue in a given moment.
"""
return random.random()>self.angelicity
def wins_against(self, other):
"""returns True if self wins in a competition against other.
See _WIN_PROB for the rules.
"""
return _WIN_PROB[self.is_rogue(), other.is_rogue()]>random.random()
class Cohort:
"""A model for a cohort advancing through society.
That's a bunch of people that keeps competing against each other for
advancement. As usual in this type of society, the losers are thrown
out.
So, essentially, this is a model for a single group progessing through the
strata of society.
It is constructed with an initial size.
"""
# draw returns an angelicity. For starters, we draw from a uniform
# distribution
draw = random.random
def __init__(self, init_size):
self.actors = set(Actor(self.draw())
for _ in range(init_size))
def iter_pairs(self):
"""iterates of pairs of our actors.
This isn't really random, as we're just using order python puts the
sets in, but I guess that reflects well how such competitions work in
reality.
If there's an odd number of actors, the last one gets silently dropped.
"""
# we need the list here as we don't want to fail if actor vanish
# in between: dead men may fight in our simulation!
to_return = iter(list(self.actors))
return zip(to_return, to_return)
def run_competition(self):
"""runs a single competition for advancing to the next stage.
We draw random pairs, let the two actors compete, and discard the
loser. Similarities to the real world are intended.
Anyone left without a partner to compete against is discarded.
"""
new_actors = set()
for a1, a2 in self.iter_pairs():
if a1.wins_against(a2):
new_actors.add(a1)
else:
new_actors.add(a2)
self.actors = new_actors
def get_meanness(self):
"""returns the mean angelicity of the cohort.
"""
return 1-sum(a.angelicity for a in self.actors)/len(self.actors)
def main():
cohort = Cohort(2**20)
for round in range(32):
cohort.run_competition()
print(round, cohort.get_meanness())
if __name__=="__main__":
main()
# vim:et:sta:sw=4