Game of Oligarchy Part 3
The last round of messing about with this program. I mentioned in the second installment that I wanted to have some animation in the finished version to make the state of play at any one time more intuitive to the viewer.
Here I have laid the players out in a line and kept the colour coding as before but in addition I am showing them as coloured circles and dynamically inflating or deflating the size of each player based on their current balance.
Over time the "winners" will tend to dominate the screen and the "losers" will shrink and eventually disappear.
I've included the whole code here because I needed to make changes to the Player and the Game to be able to run the inflation/deflation animation distinct from the individual pairings.
from decimal import *
class Player(object):
def __init__(self, number, x, y):
self.number = number
self.pos = PVector(x, y)
self.money = 100
self.diameter = 100.0
def __str__(self):
return 'player ' + str(self.number) + ' has ' + str(self.money)
def colour_for(self, value):
opacity = 100
colour = color(128, 128, 128, opacity)
if value > 0:
if value > 10:
if value > 100:
colour = color(0, 255, 0, opacity)
else:
colour = color(0, 128, 0, opacity)
else:
colour = color(128, 0, 0, opacity)
return colour
# don't run a game while animation is happening
def is_changing(self):
return self.diameter != self.money
def display(self):
rectMode(CENTER)
colour = self.colour_for(self.money)
fill(colour)
noStroke()
ellipse(self.pos.x, self.pos.y, float(self.diameter), float(self.diameter))
inflation_factor = 10
if self.diameter > self.money:
self.diameter = max(self.money, self.diameter - inflation_factor)
elif self.diameter < self.money:
self.diameter = min(self.money, self.diameter + inflation_factor)
# dodgy player number display
textSize(24)
fill(color(255))
text(str(self.number), self.pos.x, self.pos.y)
def is_playing(self):
return self.money > 0
def stake(self):
amount = Decimal(self.money / 2).to_integral_value()
amount = max(amount, 1)
println(str(self) + ' stakes ' + str(amount))
return amount
def lose(self, amount):
println(str(self) + ', loses ' + str(amount))
self.money = max(self.money - amount, 0)
def win(self, amount):
println(str(self) + ', wins ' + str(amount))
self.money += amount
players = [ ]
coin_options = 'heads', 'tails'
def setup():
fullScreen()
player_number = 1
for x in range(1, 10):
# rough layout
p = Player(player_number, x * 50, 450)
players.append(p)
player_number += 1
def draw():
background(255)
changing = False
for p in players:
if p.is_changing():
changing = True
break
# are we animating or playing a round?
if changing == False:
# allocate players to pairs
unpaired = []
paired = {}
for p in players:
if p.is_playing():
unpaired.append(p)
# pair them off
while len(unpaired) > 1:
firstChoice = int(random(len(unpaired)))
player1 = unpaired.pop(firstChoice)
secondChoice = int(random(len(unpaired)))
player2 = unpaired.pop(secondChoice)
paired[player1] = player2
# play each pair
for p in paired:
player1 = p
player2 = paired[p]
bet = min(player1.stake(), player2.stake())
# toss a coin
pick = int(random(len(coin_options)))
heads_or_tails = coin_options[pick]
println('coin is ' + heads_or_tails)
if heads_or_tails == 'tails':
player1.win(bet)
player2.lose(bet)
else:
player1.lose(bet)
player2.win(bet)
# layout each circle in a line.
# find largest so that we don't go off canvas
max_height = 0
for p in players:
max_height = max(max_height, p.pos.y)
for p in players:
p.pos.y = max_height
# layout in a line
# show each status regardless of whether they played
# in the last round
x = float(0.0)
for p in players:
p.pos.x = float(x) + float(p.diameter / 2)
println('player x is ' + str(p.pos.x) + ' with diameter ' + str(p.diameter))
x += float(p.diameter)
for p in players:
p.display()
saveFrame("grid-######.png")
I'm not super happy with the code to do the layout of the circles and them moving around dynamically in response to the wins and losses. This could stand to be reworked at another time when I learn more about making processing code more elegant.