“The rich get richer.” I propose to pivot to implement a simple but interesting simulation. A good start after the usual trouble creating a new PyCharm project.

Full Disclosure
There is a serious defect in the code, which I’ll discover four articles from now. The effect of the defect is that certain players are more likely to win than others: the game isn’t fair. As we’ll see, all that really does is speed up the rich getting richer. They still win all the chips even with the defect fixed.

For extra credit, see if you can spot the defect before I did. With that hint it should be easy.

There’s this saying, “the rich get richer”, and it seems to be true. A while back, I found an article and simulation on line that demonstrated this. I can’t find it now, although I have found some related articles, which I’ll link below. If you recall the one I mean and let me know, I’ll link it in as well: it explained things very nicely as I recall.

The fundamental idea is called the “yard sale model”, and this diagram

flow chart

from the first article linked below, shows a simple flow chart.

My plan here is to build a little simulation of this model, showing balls bouncing around in a box. When two balls collide, they’ll perform a transaction according to the rules of the simulation, which transfers wealth from one to the other. We’ll show each ball’s wealth by its size. The results, I think, will be interesting and a bit concerning.

The wealth transfer rule is simple. When two interact, a fair coin is flipped, and a fixed percentage of the wealth of the least wealthy one is transferred in one direction or the other. At first blush, this seems fair, because the coin is fair, so that you’d think it would all tend to average out. We’ll see if that’s what really happens.

The on-line versions of the yard sale model that I’ve found all just do the simulation on an array of players, and show a histogram of the wealth of all the players, sorted low to high. Pairs are selected randomly. Our pairs will also be selected randomly, but the selection will be from collisions between the balls bouncing around.

Aside
It occurs to me that a larger ball might get an unfair number of transactions since it is easier for it to collide with others. We’ll look for that effect, but even so it seems that the transactions should average out. (Spoiler: they won’t.)

Let’s get to work.

In a new project …

class TestPerson:
    def test_hookup(self):
        assert 2 + 2 == 4

    def test_person(self):
        p = Person()
        q = Person()
        p.transact(q, 1)
        assert p.wealth == 1100
        assert q.wealth == 900

And

class Person:
    def __init__(self):
        self.wealth = 1000

    def bet(self):
        return 0.1*self.wealth

    def transact(self, other, prob=None):
        prob = prob if prob else random()
        bet = min(self.bet(), other.bet())
        if prob:
            self.wealth += bet
            other.wealth -= bet
        else:
            other.wealth += bet
            self.wealth -= bet

I’ve set up transact so that we can send in the result of the random roll, for testing. In this case, p won. Now the other test.

    def test_person_q_wins(self):
        p = Person()
        q = Person()
        p.transact(q, 0)
        assert p.wealth == 900
        assert q.wealth == 1100

Fails. Why? Because 0 causes a re-roll. Fix:

    def transact(self, other, prob=None):
        prob = prob if prob is not None else random()
        bet = min(self.bet(), other.bet())
        if prob:
            self.wealth += bet
            other.wealth -= bet
        else:
            other.wealth += bet
            self.wealth -= bet

This could all be nicer, but I am in a hurry to get things actually working on screen. For that we need pygame and a game class. No pytests for that, I’m afraid. (And I am afraid, but not very.)

After a bit of FAFO, I have this:

from game import Game

if __name__ == "__main__":
    yardsale = Game()
    yardsale.main_loop()

class Game:
    def __init__(self):
        pygame.init()
        pygame.display.set_caption("Wealth")
        self.screen = pygame.display.set_mode((512, 512))
        self.clock = pygame.time.Clock()

    def main_loop(self):
        running = True
        while running:
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    running = False
            screen = self.screen
            screen.fill("midnightblue")
            pygame.draw.circle(screen, "white", (256,256), 50, 1)
            self.clock.tick(60)
            pygame.display.flip()
        pygame.quit()

And on screen:

first screen with white circle blue background

I think that almost all that boilerplate is necessary. For sure we need the event loop: without it I was getting no display at all.

Anyway we are under way now. Let’s cause Game to create some number of people, which we will teach to draw themselves. In this tiny program, I see no need for any fancy separate view kinds of things. We’ll see what we discover.

I only get seconds into that when I decide, no, I want a PersonView object here.

class PersonView:
    def __init__(self, x, y):
        self.pos = (x, y)

    def draw(self, screen):
        pygame.draw.circle(screen, "red", self.pos, 10, 0)

class Game:
    def __init__(self):
        pygame.init()
        pygame.display.set_caption("Wealth")
        self.screen = pygame.display.set_mode((512, 512))
        self.clock = pygame.time.Clock()
        self.people = []
        self.populate()

    def populate(self):
        for i in range(100):
            self.add_person(self.people)

    def add_person(self, people):
        x = random.uniform(0, 512)
        y = random.uniform(0, 512)
        people.append(PersonView(x,y))


    def main_loop(self):
        running = True
        while running:
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    running = False
            screen = self.screen
            screen.fill("midnightblue")
            pygame.draw.circle(screen, "white", (256,256), 50, 1)
            for pv in self.people:
                pv.draw(screen)
            self.clock.tick(60)
            pygame.display.flip()
        pygame.quit()

I’ve paid no attention to overlap among the circles, nor hitting the edges. I’m trying to get from not working to working in as small steps as seem reasonable. Here’s the result:

dark blue square with many red dots randomly placed, and a white circle

Let’s give each of our PersonViews a trivial width function and the ability to know whether it is colliding with another pv.

class PersonView:
    def __init__(self, x, y):
        self.pos = Vector2(x, y)
        self.radius = 10

    def colliding(self, aPersonView):
        dist = self.pos.distance_to(aPersonView.pos)
        return dist <= self.radius + aPersonView.radius

I should have test-driven the above! I had the test reversed for some reason. A simple test would have told me that. But I was confident that I had done it right. The result was that what I did next didn’t work. I want to ensure that none of the PVs are intersecting when the game begins:


class Game:
    def __init__(self):
        pygame.init()
        pygame.display.set_caption("Wealth")
        self.screen = pygame.display.set_mode((512, 512))
        self.clock = pygame.time.Clock()
        self.people = []
        self.populate()

    def populate(self):
        for i in range(100):
            self.add_person(self.people)

    def add_person(self, people):
        safe = False
        while not safe:
            safe = True
            x = random.uniform(0, 512)
            y = random.uniform(0, 512)
            trial = PersonView(x, y)
            for person in people:
                if person.colliding(trial):
                    safe = False
        people.append(trial)

This works now:

lots of dots, none intersecting each other

The code is messy and needs improvement. And it’s doing what I’ve asked it to, so far. I need a break. Let’s sum up.

Summary

One method that actually does anything, almost trivially short, and I got it wrong. Had I written even one test, I’d have found the problem instantly. As it was, I wasted a lot of time trying to figure out what was wrong with that safe loop … and the answer was NOTHING.

I had difficulty setting up the project. There is some simple incantation that I do not know, but backing off and working through a PyCharm instruction page on pytest got me going. I think the issue has to do with setting up the folders, because I’d swear that my configuration for the tests was the same as the one that’s working, but it didn’t work.

Anyway, we’re good now and I’ll put the code up on GitHub, probably next time.

Remaining to be done:

  1. Keep away from the edges.
  2. Plug Person into PersonView to do the working part.
  3. Make the balls move and collide.
  4. Scaling.
  5. Profit!

See you next time!


  1. Kinetics of Wealth and the Pareto law
  2. Why the Super Rich are Inevitable.
  3. The Mathematics of Poverty, Inequality, and Oligarchy
  4. A Theory Of Wealth Inequality Is The Flip Of A Coin