5

I'm writing a Conway's Game of Life implementation. My first attempt was just to plot the board after each update using matplotlib's imshow on a NxN board of 1's and 0's. However, this didn't work as the program pauses whenever it shows the plot. You have to close the plot to get the next loop iteration.

I found out there was an animation package in matplotlib, but it doesn't take (or give) variables, so every implementatin of it I've seen (even matplotlib's documentation) relies on global variables.

So there's kind of two questions here:

1) Is this a place where it's ok to use globals? I've always read that it's never a good idea, but is this just dogma?

2) how would you do such an animation in python without globals (Even if it means ditching matplotlib, I guess; standard library is always preferred).

Keegan Keplinger
  • 627
  • 5
  • 15

2 Answers2

5

These are just example programs. You can use an object instead of global variables, like this:

class GameOfLife(object):
    def __init__(self, initial):
        self.state = initial
    def step(self):
        # TODO: Game of Life implementation goes here
        # Either assign a new value to self.state, or modify it
    def plot_step(self):
        self.step()
        # TODO: Plot here

# TODO: Initialize matplotlib here
initial = [(0,0), (0,1), (0,2)]
game = GameOfLife(initial)
ani = animation.FuncAnimation(fig, game.plot_step)
plt.show()

If you really want to avoid classes, you can also rewrite the program like this:

def step(state):
    newstate = state[:] # TODO Game of Life implementation goes here
    return newstate
def plot(state):
    # TODO: Plot here
def play_game(state):
    while True:
         yield state
         state = step(state)

initial = [(0,0), (0,1), (0,2)]
game = play_game(initial)
ani = animation.FuncAnimation(fig, lambda: next(game))
plt.show()

Note that for non-mathematical animations (without labels, graphs, scales, etc.), you may prefer pyglet or pygame.

phihag
  • 278,196
  • 72
  • 453
  • 469
  • I assumed the easiest method was imshow or pcolor functions with game of life type data (a grid of binary integers). In pygame, I thought, I would have to draw the board myself. I just recently watched a video "stop using classes" so I've been class-adverse lately and was kind of subconsciously tackling it without classes. Is the class here useful because it's the most convenient way to get an output from the animation (that's basically my problem, trying to feedback the output state back into the step of the program: you can give it inputs, but how do you get the updated state?). – Keegan Keplinger Jul 07 '13 at 12:23
  • In your example, how is the step method ever called? I don't see anything about it in [FuncAnimation documentation](http://matplotlib.org/api/animation_api.html?highlight=funcanimation#matplotlib.animation.FuncAnimation). – Keegan Keplinger Jul 07 '13 at 12:27
  • Please don't follow advice like "Stop using classes" without understanding why. While you could certainly use a classless approach here as well, that would most likely end up messier than this. `FuncAnimation` expects an arbitrary callable, and this answer passes in the function `plot_step` of a (here: the only) `GameOfLife` object, which itself calls `step`. – phihag Jul 07 '13 at 12:52
  • It appears to me that the only reason *to* use a class here is for the same reason you'd use a global. So that you can get information about the output of the state back into it without halting the program. Is there really no simple way to get a return from the animation call? If there is, you could easily reduce this in lines (and complexity) using the classless approach. There is really no other reason to use classes for this kind of exercise. – Keegan Keplinger Jul 07 '13 at 13:03
  • 2
    I guess, for me, that is the problem with both solutions (global and class) or maybe just with the animation method in general: With my functional programming training, my instinct is to want to explicitly see the output for each iteration and hand it back to the game of life function in a while loop. – Keegan Keplinger Jul 07 '13 at 13:17
  • The problem is that matplotlib's animation `func` is decidedly non-functional. Updated the answer with a classless solution. – phihag Jul 07 '13 at 13:34
  • I see, thanks for the discussion. So this conversation has got me wondering, are classes basically just an elaborate global data structure? Is that one of the defining differences between functional and classy. Btw, I am not anti-class, I have just been through a long month of class exercises (and I will return) but I'm currently trying to reproduce stuff I can do easily in Matlab in Python. – Keegan Keplinger Jul 07 '13 at 13:50
  • No, at least in Python, classes are objects like anything else. (Python doesn't really have globals (across a whole program), unless you count the built-in modules, but it has module-level globals). – phihag Jul 07 '13 at 13:53
  • Ok, I finally was able to implement your solution, it took me forever to realize that grid[x-1:x+1,y-1:y+1] was only a 2x2 grid and not the 3x3 I expected! Thanks for your help. – Keegan Keplinger Jul 07 '13 at 21:27
1

Here is an example of using FuncAnimation without a custom class:

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation

def animate(data, im):
    im.set_data(data)

def step():
    while True:
        # replace this with Conway's Game of Life logic
        data = np.random.randn(10, 10)
        yield data

fig, ax = plt.subplots()
im = ax.imshow(np.random.randn(10, 10), interpolation='nearest')
ani = animation.FuncAnimation(
    fig, animate, step, interval=10, repeat=True, fargs=(im, ))
plt.show()

When you use yield (as opposed to return) in a function, it makes the function a generator. Generator functions save state. Each time you call next on the iterator returned by the generator, the flow of execution picks up where it left off (from the last yield expression). This is the why generators are a way to avoid globals -- the would-be globals are just local variables inside the generator function, and their state is saved between calls to next.


By the way, the admonition, "never use globals" is not precise enough. We use globals all the time. Every time you import a module at the module level, the module object is a global. Every time you define a function or class at the module level, it is a global. There is nothing wrong with using globals (although it is true that accessing globals from inside a function is slower than accessing the function's locals. Nevertheless, beware of pre-optimization).

Perhaps instead the admonition should read, "Try never to use globals that change state." The reason why changing global values is bad is because every function that changes the global becomes silently related. The function no longer can be understood and tested as an isolated unit of code. The primary tool programmers use to solve problems is to break big problems down into smaller pieces. Functions and classes help break problems into smaller pieces. But when you use mutating globals, you lose this benefit. The mind must now grok the entire module at once to understand the code.

unutbu
  • 842,883
  • 184
  • 1,785
  • 1,677
  • I notice both functional solution use [yield](http://stackoverflow.com/questions/231767/the-python-yield-keyword-explained) so I'm reading up on it now, thanks. – Keegan Keplinger Jul 07 '13 at 13:58
  • it seems similar to the function handle, [@](http://www.mathworks.com/help/matlab/ref/function_handle.html), in Matlab – Keegan Keplinger Jul 07 '13 at 14:09
  • I don't know Matlab, but it seems, judging from the link you provide, that @ is used to create "function handles" so you can pass functions as arguments, and to create anonymous functions. In Python, functions are already "first-class objects" -- they can passed as arguments to other functions; no @ notation needed. And in Python, anonymous functions are created using `lambda`. So I don't think of @ as being analogous to `yield`. – unutbu Jul 07 '13 at 18:04
  • I think you're right, though I think specific implementations of @ could take the place of yield. I probably need to understand yield better first, though. – Keegan Keplinger Jul 07 '13 at 21:29
  • 1
    @KeeganKeplinger My understanding of @ in MATLAB is just what python does with functions naturally (that is makes them a value you can pass around). – tacaswell Aug 04 '13 at 06:16