.. only:: html .. note:: :class: sphx-glr-download-link-note Click :ref:`here ` to download the full example code .. rst-class:: sphx-glr-example-title .. _sphx_glr_gallery_event_handling_pipong.py: ====== Pipong ====== A Matplotlib based game of Pong illustrating one way to write interactive animation which are easily ported to multiple backends pipong.py was written by Paul Ivanov .. code-block:: default import numpy as np import matplotlib.pyplot as plt from numpy.random import randn, randint from matplotlib.font_manager import FontProperties instructions = """ Player A: Player B: 'e' up 'i' 'd' down 'k' press 't' -- close these instructions (animation will be much faster) press 'a' -- add a puck press 'A' -- remove a puck press '1' -- slow down all pucks press '2' -- speed up all pucks press '3' -- slow down distractors press '4' -- speed up distractors press ' ' -- reset the first puck press 'n' -- toggle distractors on/off press 'g' -- toggle the game on/off """ class Pad: def __init__(self, disp, x, y, type='l'): self.disp = disp self.x = x self.y = y self.w = .3 self.score = 0 self.xoffset = 0.3 self.yoffset = 0.1 if type == 'r': self.xoffset *= -1.0 if type == 'l' or type == 'r': self.signx = -1.0 self.signy = 1.0 else: self.signx = 1.0 self.signy = -1.0 def contains(self, loc): return self.disp.get_bbox().contains(loc.x, loc.y) class Puck: def __init__(self, disp, pad, field): self.vmax = .2 self.disp = disp self.field = field self._reset(pad) def _reset(self, pad): self.x = pad.x + pad.xoffset if pad.y < 0: self.y = pad.y + pad.yoffset else: self.y = pad.y - pad.yoffset self.vx = pad.x - self.x self.vy = pad.y + pad.w/2 - self.y self._speedlimit() self._slower() self._slower() def update(self, pads): self.x += self.vx self.y += self.vy for pad in pads: if pad.contains(self): self.vx *= 1.2 * pad.signx self.vy *= 1.2 * pad.signy fudge = .001 # probably cleaner with something like... if self.x < fudge: pads[1].score += 1 self._reset(pads[0]) return True if self.x > 7 - fudge: pads[0].score += 1 self._reset(pads[1]) return True if self.y < -1 + fudge or self.y > 1 - fudge: self.vy *= -1.0 # add some randomness, just to make it interesting self.vy -= (randn()/300.0 + 1/300.0) * np.sign(self.vy) self._speedlimit() return False def _slower(self): self.vx /= 5.0 self.vy /= 5.0 def _faster(self): self.vx *= 5.0 self.vy *= 5.0 def _speedlimit(self): if self.vx > self.vmax: self.vx = self.vmax if self.vx < -self.vmax: self.vx = -self.vmax if self.vy > self.vmax: self.vy = self.vmax if self.vy < -self.vmax: self.vy = -self.vmax class Game: def __init__(self, ax): # create the initial line self.ax = ax ax.set_ylim([-1, 1]) ax.set_xlim([0, 7]) pad_a_x = 0 pad_b_x = .50 pad_a_y = pad_b_y = .30 pad_b_x += 6.3 # pads pA, = self.ax.barh(pad_a_y, .2, height=.3, color='k', alpha=.5, edgecolor='b', lw=2, label="Player B", animated=True) pB, = self.ax.barh(pad_b_y, .2, height=.3, left=pad_b_x, color='k', alpha=.5, edgecolor='r', lw=2, label="Player A", animated=True) # distractors self.x = np.arange(0, 2.22*np.pi, 0.01) self.line, = self.ax.plot(self.x, np.sin(self.x), "r", animated=True, lw=4) self.line2, = self.ax.plot(self.x, np.cos(self.x), "g", animated=True, lw=4) self.line3, = self.ax.plot(self.x, np.cos(self.x), "g", animated=True, lw=4) self.line4, = self.ax.plot(self.x, np.cos(self.x), "r", animated=True, lw=4) # center line self.centerline, = self.ax.plot([3.5, 3.5], [1, -1], 'k', alpha=.5, animated=True, lw=8) # puck (s) self.puckdisp = self.ax.scatter([1], [1], label='_nolegend_', s=200, c='g', alpha=.9, animated=True) self.canvas = self.ax.figure.canvas self.background = None self.cnt = 0 self.distract = True self.res = 100.0 self.on = False self.inst = True # show instructions from the beginning self.background = None self.pads = [Pad(pA, pad_a_x, pad_a_y), Pad(pB, pad_b_x, pad_b_y, 'r')] self.pucks = [] self.i = self.ax.annotate(instructions, (.5, 0.5), name='monospace', verticalalignment='center', horizontalalignment='center', multialignment='left', textcoords='axes fraction', animated=False) self.canvas.mpl_connect('key_press_event', self.on_key_press) def draw(self, event): draw_artist = self.ax.draw_artist if self.background is None: self.background = self.canvas.copy_from_bbox(self.ax.bbox) # restore the clean slate background self.canvas.restore_region(self.background) # show the distractors if self.distract: self.line.set_ydata(np.sin(self.x + self.cnt/self.res)) self.line2.set_ydata(np.cos(self.x - self.cnt/self.res)) self.line3.set_ydata(np.tan(self.x + self.cnt/self.res)) self.line4.set_ydata(np.tan(self.x - self.cnt/self.res)) draw_artist(self.line) draw_artist(self.line2) draw_artist(self.line3) draw_artist(self.line4) # pucks and pads if self.on: self.ax.draw_artist(self.centerline) for pad in self.pads: pad.disp.set_y(pad.y) pad.disp.set_x(pad.x) self.ax.draw_artist(pad.disp) for puck in self.pucks: if puck.update(self.pads): # we only get here if someone scored self.pads[0].disp.set_label( " " + str(self.pads[0].score)) self.pads[1].disp.set_label( " " + str(self.pads[1].score)) self.ax.legend(loc='center', framealpha=.2, facecolor='0.5', prop=FontProperties(size='xx-large', weight='bold')) self.background = None self.ax.figure.canvas.draw_idle() return True puck.disp.set_offsets([[puck.x, puck.y]]) self.ax.draw_artist(puck.disp) # just redraw the axes rectangle self.canvas.blit(self.ax.bbox) self.canvas.flush_events() if self.cnt == 50000: # just so we don't get carried away print("...and you've been playing for too long!!!") plt.close() self.cnt += 1 return True def on_key_press(self, event): if event.key == '3': self.res *= 5.0 if event.key == '4': self.res /= 5.0 if event.key == 'e': self.pads[0].y += .1 if self.pads[0].y > 1 - .3: self.pads[0].y = 1 - .3 if event.key == 'd': self.pads[0].y -= .1 if self.pads[0].y < -1: self.pads[0].y = -1 if event.key == 'i': self.pads[1].y += .1 if self.pads[1].y > 1 - .3: self.pads[1].y = 1 - .3 if event.key == 'k': self.pads[1].y -= .1 if self.pads[1].y < -1: self.pads[1].y = -1 if event.key == 'a': self.pucks.append(Puck(self.puckdisp, self.pads[randint(2)], self.ax.bbox)) if event.key == 'A' and len(self.pucks): self.pucks.pop() if event.key == ' ' and len(self.pucks): self.pucks[0]._reset(self.pads[randint(2)]) if event.key == '1': for p in self.pucks: p._slower() if event.key == '2': for p in self.pucks: p._faster() if event.key == 'n': self.distract = not self.distract if event.key == 'g': self.on = not self.on if event.key == 't': self.inst = not self.inst self.i.set_visible(not self.i.get_visible()) self.background = None self.canvas.draw_idle() if event.key == 'q': plt.close() .. _sphx_glr_download_gallery_event_handling_pipong.py: .. only :: html .. container:: sphx-glr-footer :class: sphx-glr-footer-example .. container:: sphx-glr-download sphx-glr-download-python :download:`Download Python source code: pipong.py ` .. container:: sphx-glr-download sphx-glr-download-jupyter :download:`Download Jupyter notebook: pipong.ipynb ` .. only:: html .. rst-class:: sphx-glr-signature Keywords: matplotlib code example, codex, python plot, pyplot `Gallery generated by Sphinx-Gallery `_