How Fair is It?
Changing the rules a bit, yesterday, limits the richest player to around twice the starting amount. But are there still permanent winners and losers?
I propose to retain the minimum and maximum wealth for each Person, and to display it on screen. Should be easy enough.
Currently, ‘wealth’ is an attribute of Person:
class Person:
def __init__(self):
self.wealth = 1000
We have some tests for the Person class, so let’s write a new one:
def test_min_max(self):
p = Person()
assert p.wealth == 1000
assert p.min_wealth == 1000
assert p.max_wealth == 1000
That will drive out the new values:
def __init__(self):
self.wealth = 1000
self.min_wealth = self.wealth
self.max_wealth = self.wealth
Now for the ups and downs:
def test_min_max(self):
p = Person()
assert p.wealth == 1000
assert p.min_wealth == 1000
assert p.max_wealth == 1000
q = Person()
p.transact(q, 1)
assert p.wealth == 1100
assert p.max_wealth == 1100
I was a bit surprised not to need to do a range check on wealth since we are multiplying it by 0.1 to get the bet, but I’ll take the result and deal with approximations as needed.
Now what about the max? We currently adjust wealth like this:
def transact(self, other, prob=None):
prob = prob if prob is not None else random()
# bet = min(self.bet(), other.bet())
if prob > 0.5:
self.wealth += other.bet()
other.wealth -= other.bet()
else:
other.wealth += self.bet()
self.wealth -= self.bet()
It’s not desirable to go in there and fiddle all the max and min. Instead, let’s make wealth a property, so that we can associate behavior with its setting.
class Person:
def __init__(self):
self._wealth = 1000
self.min_wealth = self.wealth
self.max_wealth = self.wealth
@property
def wealth(self):
return self._wealth
@wealth.setter
def wealth(self, value):
self._wealth = value
self.min_wealth = min(self.min_wealth, value)
self.max_wealth = max(self.max_wealth, value)
Since I did them both, got ahead of my test there, I’ll update the test and then move on to display.
def test_min_max(self):
p = Person()
assert p.wealth == 1000
assert p.min_wealth == 1000
assert p.max_wealth == 1000
q = Person()
p.transact(q, 1)
assert p.wealth == 1100
assert p.max_wealth == 1100
assert q.wealth == 900
assert q.min_wealth == 900
Close enough. Now let’s see about adding those values to our histogram.
Here, we are not so fortunate. The game does all the drawing, and it only concerns itself with the values of wealth:
class Game:
def statistics(self, views, screen):
pygame.draw.line(screen, "green", (0, screen_size), (screen_size - 55, screen_size))
wealths = sorted([view.person.wealth for view in views])
poorest, richest = self.get_loser_and_winner(views, wealths)
self.display_loser_and_winner(poorest, richest, screen)
self.draw_histogram(richest, wealths, screen)
def draw_histogram(self, richest, wealths, screen):
self.draw_bars(wealths, richest, screen)
self.draw_scale(richest, screen)
def draw_bars(self, wealths, richest, screen):
scale = stats_space / scale_max(richest)
min_height = 2
for wealth, x_pos in zip(wealths, itertools.count(7, 7)):
self.draw_one_bar(wealth, x_pos, scale, min_height, screen)
def draw_one_bar(self, wealth, x_pos, scale, min_height, screen):
height_of_bar = max(wealth * scale, min_height)
bottom_of_graph = screen_size + stats_space
top_of_bar = bottom_of_graph - height_of_bar
pygame.draw.rect(screen, "white", (x_pos, top_of_bar, 5, height_of_bar))
Clearly (proof left to the reader) we want our list to be a list of Person, sorted by wealth.
Let’s rename the variable first, to people_by_wealth. PyCharm can’t help much, so I’ll do the rename in the called methods as well. Here’s the meat of the change:
def draw_bars(self, people_by_wealth, richest, screen):
scale = stats_space / scale_max(richest)
min_height = 2
for person, x_pos in zip(people_by_wealth, itertools.count(7, 7)):
self.draw_one_bar(person, x_pos, scale, min_height, screen)
def draw_one_bar(self, person, x_pos, scale, min_height, screen):
height_of_bar = max(person * scale, min_height)
bottom_of_graph = screen_size + stats_space
top_of_bar = bottom_of_graph - height_of_bar
pygame.draw.rect(screen, "white", (x_pos, top_of_bar, 5, height_of_bar))
Of course, person
there is still a wealth. I really wish I had more tests right now, but we’ll just break it and fix it.
The first challenge is this one:
people_by_wealth = sorted([view.person.wealth for view in views])
Fortunately, the sorted
function can take a key. So how about this:
def statistics(self, views, screen):
pygame.draw.line(screen, "green", (0, screen_size), (screen_size - 55, screen_size))
people_by_wealth = sorted([view.person for view in views], key=lambda person: person.wealth)
poorest, richest = self.get_loser_and_winner(views, people_by_wealth)
self.display_loser_and_winner(poorest, richest, screen)
self.draw_histogram(richest, people_by_wealth, screen)
Making that work was more trouble than I want to report here: we are mostly interested in the result. There is a lesson for the programmers among us, however, which is that no matter what you tell yourself about how this script can be quick and dirty because it’s a one-time thing, it probably isn’t a one-time thing.
In any case, I’ve got the simulation running again, so let’s change the bars. We want to show, for each individual, their minimum wealth, their current wealth, and their maximum wealth. Here’s how we draw the current bar. Let’s try this: we’ll draw in red, from zero to min, white from min to current, green from current to max. We’re starting here:
def draw_one_bar(self, person, x_pos, scale, min_height, screen):
height_of_bar = max(person.wealth * scale, min_height)
bottom_of_graph = screen_size + stats_space
top_of_bar = bottom_of_graph - height_of_bar
pygame.draw.rect(screen, "white", (x_pos, top_of_bar, 5, height_of_bar))
My first cut at just typing it all in fails. So I’ll scribble on a card for a bit. It seems to me that we want to draw from max down to current in green, current down to min in white, min down to zero in red.
We draw down to because the bloody screen has zero at the top like no graph ever.
def draw_one_bar(self, person, x_pos, scale, min_height, screen):
bottom_of_graph = screen_size + stats_space
max_scaled = person.max_wealth * scale
cur_scaled = person.wealth * scale
min_scaled = person.min_wealth * scale
top_of_max = bottom_of_graph - max_scaled
top_of_cur = bottom_of_graph - cur_scaled
top_of_min = bottom_of_graph - min_scaled
pygame.draw.rect(screen, "green", (x_pos, top_of_max, 5, max_scaled - cur_scaled))
pygame.draw.rect(screen, "white", (x_pos, top_of_cur, 5, cur_scaled - min_scaled))
pygame.draw.rect(screen, "red", (x_pos, top_of_min, 5, min_scaled))
This nearly works. After we run for a while, the picture looks like this:
Just about everyone has been up at some point in the game. I want to fix up the scale, however, because sometimes, when the current wealthiest person is less wealthy than others have been in the past, the bars go outside the plot window. I’ll work on that later. After we run the game for a decent interval, the picture looks like this:
I think the distribution might be a bit more flat than we had before Bruce’s suggestion. A cynical reading of the graph, since almost everyone shows some green, might be “almost everyone is worse off than they once were”. That’s kind of depressing, along the lines of “in the long run we’ll all”, well, you know.
I want to do a little summary for programmers and then a real summary.
Summary for Programmers
My ad-hoc scaling and graphics held up well until today. Today, it almost held up, but not quite. What you don’t see in my screen shots is that sometimes the graph pops up far above the green dividing line, and as I write this, I do not understand why. I offer two lessons to myself, and if you have more, please pass them on.
- Better testing for the scaling and drawing would be very valuable.
- Deferring drawing of the histogram to the individual view or person would likely make testing easier.
- A more general scaling approach that applied scaling and translation at the last minute would probably help.
- Making a save point early in this article would have facilitated rolling back and starting the scaling over.
(As usual, I came up with more than the predicted two.)
The “big” lesson, as I mentioned above, is that no matter how one-off we think a script is, we are very likely to revisit it and to need to change it. As such, while it can be quick and dirty, we probably don’t want to leave it quick and filthy. A bit of care is likely to pay off. Failing that, when we do revisit it, we might take that as a sign and do a bit of cleanup before we even start.
Will we do that? No, probably not. But if we did, I think things would go a bit better. YMMV, of course.
Summary
We can’t read too much into this tiny, overly-simple exercise. But it does show how even very simple rules can result in quite surprising results. Our original version wound up with one individual holding about forty percent of all the wealth in the world. Our simple, fair-seeming game inevitably results in the rich getting richer. We should not read much into the fact that in our game, it’s all a matter of luck, and how much money you already have.
Surely that’s not true in real life? Surely the rich are somehow smarter or more capable than the rest of us. It couldn’t all come down to inheritance and luck … could it?
In our own world, the top ten percent of people hold two thirds of the total wealth. Two thirds! Sixty-seven percent. The top one percent hold almost a third, thirty-one percent.
I think the game is rigged, and I think that, unfortunately, we have lawmakers on both sides who are, or who aspire to be, well off. With the best of will—for those who have the best of will—they’ll rarely vote to harm themselves, and thus the laws will continue to support this gross inequity.
What can be done about it? Well, for now, I’d suggest voting for those who seem to do best for you, even if they are far from perfect. I’d suggest voting for someone who can possibly win, even though you might think that some third-party person would be objectively better. (Currently, I don’t think we have a problem of that kind.)
We need to change the system, or soon we’ll be sending Tribute to the Hunger Games. I would prefer that we change the system in an orderly fashion, because a revolution is unlikely to be good for the small folks.
This game means nothing, but it might make you think. And, I hope, it’ll make you vote.
Post Script
Here is a picture after a very long run: hours in fact.
It seems to me that we see that over time, pretty much anyone will experience a good run and their wealth will top out at or near the maximum that anyone attains, and that, over time, they’ll be drawn back down to the norm. It’s a bit difficult to see that as “good”, but in fact it seems to me that it’s saying that everyone has an equal chance at the big time, and that people who hit the big time don’t just stay there by virtue of being big. In a growing economy, I’d expect everyone to advance more or less equally. But that’s far beyond this model.
What do you see? Does this exercise help you think about the world? Does it suggest actions you might want to take?