I’m revisiting some older research of mine, so that I can talk a little bit about some data visualization I did along the way. If you frequent TriZPUG or the SplatSpace, you might have seen my original presentation, but In Case You Missed It…
You might remember a while back I got interested in researching the statistical thermodynamics of poker tournaments. To briefly recap, I was treating the distribution of chips amongst players as a probability distribution, which meant that I could use the concept of entropy to describe the distribution. Entropy in thermodynamic systems is associated with how ‘spread out’ the energy is in that system: A hot cup of coffee in a cold room has low entropy while warm coffee in a warm room has high entropy. In a statistical system like a poker table, entropy measures how evenly distributed the chips are between the players. When the players start the tournament with equal amounts, the entropy is at a maximum. When one player wins all the chips, the entropy is at a minimum. Already things are interesting – entropy in this statistical system must decrease with time, in stark contrast with the second law of thermodynamics. And we haven’t even looked at what happens between those two points!
To better understand the behavior of tournaments, I needed a way to play them and replay them, to turn them into something other than tables of names and numbers. The first representation worked well at illustrating the distribution, but failed to capture the dynamics; except in catastrophic rearrangements, it was not always obvious how the chips moved around from hand to hand.
What’s going on is, I’ve whimsically renamed the players for anonymity, and then represented the size of their stack with a circle. Each hand is then represented by a transaction in which chips flow from one or more players to a single winner, with chip flow represented by black lines whose size is representative of the magnitude of flow. I find this hypnotic.
If you don’t care about coding, feel free to skip down….
How exactly did I put this together?
Zeroeth, we have to get our tools together.
import pickle, sys #file IO utilities import pygame #pygame library from pygame.locals import * #more pygame stuff from math import sin, cos, pi, sqrt #math tools
First, there is a great deal of tedious regular expression slicing and dicing that you have to do to convert a tournament history file into usable data; I’ll be merciful and skip that. So I’ve finally bundled up the data in a couple of files.
vial = open('dat/cash_timeline.dat','r') cash = pickle.load(vial) vial.close() vial = open('dat/flux_timeline.dat','r') flux = pickle.load(vial) vial.close()
pygame.init()# start yer engines! windowSurfObj = pygame.display.set_mode([700,700])# build a window pygame.display.set_caption('Poker Face')# name it fontObj = pygame.font.Font('freesansbold.ttf', 24)# get ready to write on it
Next, we have to build a table for the players to sit at. Let’s make it a circular table. Since the players’ stacks will be represented with circles, we’ll call the table a megacircle. It’s going to be invisible, but we need to build it anyway so that the players will have a place to sit.
centerx, centery = 300,300# center of the megacircle radius = 175# size of the megacircle
Now, we have to give each player a seat at the table. We want them to be evenly spaced, so I used a trick I remembered from abstract algebra: the roots of unity of a polynomial of degree n form an n-sided polygon in the plane, and in particular have easily computed coordinates. I used this fact to arrange seats for the seven starting players (I also identify them with a color):
for i in range(0, len(allPlayers)):# give each player a color and a location. color_dict[allPlayers[i]] = colors[i]# colors for everyone! place_dict[allPlayers[i]] = (centerx+int(radius*cos(2*pi*i/len(allPlayers))), centery+int(radius*sin(2*pi*i/len(allPlayers))))# take your seats.
Now we’re ready to replay the tournament, looking at the stacks and fluxes between.
while hand < 675:# for each hand... windowSurfObj.fill(white)# erase everything win = winner()#who won this hand? for playa in allPlayers:# for all of your players, consider each individually. if flux[playa][hand] < 0.0:# did they lose money this hand? if so.... width = arr(abs(flux[playa][hand]))#then the width of the line connecting them to the winner is proportional to their loss. pygame.draw.line(windowSurfObj, black, place_dict[playa], place_dict[win], width)# draw that line.
On top of that, we draw the circles representing players’ holdings. The pygame canvas is like a real life collage; when you add something, it covers up things that were already there. The circles will cover up the rough ends that are left from drawing those lines.
for playa in allPlayers:#circle round, everyone... rad = arr(cash[playa][hand])#radius of circle pygame.draw.circle(windowSurfObj, color_dict[playa], place_dict[playa], rad)#draw circles.
Let’s also give them nametags.
for playa in allPlayers:# his name was robert paulson. msgSurfObj = fontObj.render(playa, False, graph)# type the name in graphite letters. msgRectObj = msgSurfObj.get_rect()# take the text as a whole.... msgRectObj.topleft = place_dict[playa]# .... and put it next to the player's seat windowSurfObj.blit(msgSurfObj, msgRectObj)# this basically just glues everything together.
…and then there’s a bit of mostly vestigial keybindings, not interesting enough to really talk about. Next we update the display, which sort of finalizes what we’re going to see on the screen:
And then I save each frame as a single image, like a slide in a slideshow.
Finally, the moment we’ve all been waiting for: We’ve drawn the slide, and it’s time to move on to the next:
hand += 1
Now, once we run this script, we’ll see the animation. We’ll also get each frame of the animation individually, bundled as a file. This way, we can just zip it together with a handy command-line program called ffmpeg:
>ffmpeg -fimage2 -i frame%d.jpeg -r 12 HxW demo.avi
Add some titles and background music using openshot, and you have a data visualization as open sourced as it is heart-pounding.
Note that this is a flyby, not a walkthrough, of pygame and python. For the full, original code, derps and all: http://pastebin.com/Anqe3eGS
… to here.
Part of the reason I wanted this bird’s eye view is to take a closer look at the dynamics of the game. Entropic processes often lead to what are called dissipative structures. The second law of thermodynamics insists that entropy must increase; dissipative structures are the routes and means by which this happens. The large-scale currents between the warm equator and cold poles are examples of dissipative structures; on a smaller scale, so are eddies and dust devils. Living organisms are dissipative structures which convert free energy (food) into heat.
I have already discussed Dr. Arto Annila’s paper, Economies Evolve by Energy Dispersal (Annila & Salthe 2009) which frames ecosystems and economies as dissipative structures. They explain, “Eventually, when no further and faster means to consume free energy and no additional sources are found, the economy reaches a steady-state, just as an ecosystem attains a climax, the mature state with maximal gross transduction.” I think that you can see this in the figure and the visualization. After each player loss, the system is placed out of equilibrium and has to readjust to a new steady-state.
Dr. Annila has also used thermodynamics to frame game theory, pointing to its physical science origins. (Anttila & Annila 2011). This paper explores the general concept of ‘utility’ which gaming strategies aim to maximize, proposing that the payoff for a strategy is actually the rate of entropy increase which it entails. Game theory can apply to chemistry, poker players, or ecosystems; in each case, it’s thermodynamics that motivate molecules, individuals, and populations. “…the behavior of many systems, including decision-making processes, could be described as natural processes so that entropy is the universal payoff function … The quest to consume free energy in least time is ubiquitous and independent of mechanisms.”
One observation that I think gets highlighted is the ‘ringing’ after a major transition like the loss of a player. The loss of cyan around hand #5 in figure 1 seems to trigger a bit of it – look at the oscillations in entropy as the sudden influx of free energy disperses throughout the table. Dr. Annila was kind enough to send some thoughts on this project, and it seems that such ringing might have theoretical basis as well: “At these occasions the path-dependent process display discontinuity … One could even expect to see some damping oscillations.” Watching the video animation, it is clearer exactly how that dispersal takes place, and how the network of interactions allows nonlocal connections between players, a characteristic of dissipative structures.
The good news on this front is that I’ve met a friend who is excited about sharing their collection of tournament histories. Stay tuned for updates as the numbers get crunched…
Annila, A., & Salthe, S. (2009). Economies Evolve by Energy Dispersal Entropy, 11 (4), 606-633 DOI: 10.3390/e11040606
Anttila, J., & Annila, A. (2011). Natural games Physics Letters A, 375 (43), 3755-3761 DOI: 10.1016/j.physleta.2011.08.056