{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "\n# Pong\n\nA Matplotlib based game of Pong illustrating one way to write interactive\nanimations that are easily ported to multiple backends.\n\n<div class=\"alert alert-info\"><h4>Note</h4><p>This example exercises the interactive capabilities of Matplotlib, and this\n    will not appear in the static documentation. Please run this code on your\n    machine to see the interactivity.\n\n    You can copy and paste individual parts, or download the entire example\n    using the link at the bottom of the page.</p></div>\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "import time\n\nimport matplotlib.pyplot as plt\nimport numpy as np\nfrom numpy.random import randint, randn\n\nfrom matplotlib.font_manager import FontProperties\n\ninstructions = \"\"\"\nPlayer A:       Player B:\n  'e'      up     'i'\n  'd'     down    'k'\n\npress 't' -- close these instructions\n            (animation will be much faster)\npress 'a' -- add a puck\npress 'A' -- remove a puck\npress '1' -- slow down all pucks\npress '2' -- speed up all pucks\npress '3' -- slow down distractors\npress '4' -- speed up distractors\npress ' ' -- reset the first puck\npress 'n' -- toggle distractors on/off\npress 'g' -- toggle the game on/off\n\n  \"\"\"\n\n\nclass Pad:\n    def __init__(self, disp, x, y, type='l'):\n        self.disp = disp\n        self.x = x\n        self.y = y\n        self.w = .3\n        self.score = 0\n        self.xoffset = 0.3\n        self.yoffset = 0.1\n        if type == 'r':\n            self.xoffset *= -1.0\n\n        if type == 'l' or type == 'r':\n            self.signx = -1.0\n            self.signy = 1.0\n        else:\n            self.signx = 1.0\n            self.signy = -1.0\n\n    def contains(self, loc):\n        return self.disp.get_bbox().contains(loc.x, loc.y)\n\n\nclass Puck:\n    def __init__(self, disp, pad, field):\n        self.vmax = .2\n        self.disp = disp\n        self.field = field\n        self._reset(pad)\n\n    def _reset(self, pad):\n        self.x = pad.x + pad.xoffset\n        if pad.y < 0:\n            self.y = pad.y + pad.yoffset\n        else:\n            self.y = pad.y - pad.yoffset\n        self.vx = pad.x - self.x\n        self.vy = pad.y + pad.w/2 - self.y\n        self._speedlimit()\n        self._slower()\n        self._slower()\n\n    def update(self, pads):\n        self.x += self.vx\n        self.y += self.vy\n        for pad in pads:\n            if pad.contains(self):\n                self.vx *= 1.2 * pad.signx\n                self.vy *= 1.2 * pad.signy\n        fudge = .001\n        # probably cleaner with something like...\n        if self.x < fudge:\n            pads[1].score += 1\n            self._reset(pads[0])\n            return True\n        if self.x > 7 - fudge:\n            pads[0].score += 1\n            self._reset(pads[1])\n            return True\n        if self.y < -1 + fudge or self.y > 1 - fudge:\n            self.vy *= -1.0\n            # add some randomness, just to make it interesting\n            self.vy -= (randn()/300.0 + 1/300.0) * np.sign(self.vy)\n        self._speedlimit()\n        return False\n\n    def _slower(self):\n        self.vx /= 5.0\n        self.vy /= 5.0\n\n    def _faster(self):\n        self.vx *= 5.0\n        self.vy *= 5.0\n\n    def _speedlimit(self):\n        if self.vx > self.vmax:\n            self.vx = self.vmax\n        if self.vx < -self.vmax:\n            self.vx = -self.vmax\n\n        if self.vy > self.vmax:\n            self.vy = self.vmax\n        if self.vy < -self.vmax:\n            self.vy = -self.vmax\n\n\nclass Game:\n    def __init__(self, ax):\n        # create the initial line\n        self.ax = ax\n        ax.xaxis.set_visible(False)\n        ax.set_xlim(0, 7)\n        ax.yaxis.set_visible(False)\n        ax.set_ylim(-1, 1)\n        pad_a_x = 0\n        pad_b_x = .50\n        pad_a_y = pad_b_y = .30\n        pad_b_x += 6.3\n\n        # pads\n        pA, = self.ax.barh(pad_a_y, .2,\n                           height=.3, color='k', alpha=.5, edgecolor='b',\n                           lw=2, label=\"Player B\",\n                           animated=True)\n        pB, = self.ax.barh(pad_b_y, .2,\n                           height=.3, left=pad_b_x, color='k', alpha=.5,\n                           edgecolor='r', lw=2, label=\"Player A\",\n                           animated=True)\n\n        # distractors\n        self.x = np.arange(0, 2.22*np.pi, 0.01)\n        self.line, = self.ax.plot(self.x, np.sin(self.x), \"r\",\n                                  animated=True, lw=4)\n        self.line2, = self.ax.plot(self.x, np.cos(self.x), \"g\",\n                                   animated=True, lw=4)\n        self.line3, = self.ax.plot(self.x, np.cos(self.x), \"g\",\n                                   animated=True, lw=4)\n        self.line4, = self.ax.plot(self.x, np.cos(self.x), \"r\",\n                                   animated=True, lw=4)\n\n        # center line\n        self.centerline, = self.ax.plot([3.5, 3.5], [1, -1], 'k',\n                                        alpha=.5, animated=True, lw=8)\n\n        # puck (s)\n        self.puckdisp = self.ax.scatter([1], [1], label='_nolegend_',\n                                        s=200, c='g',\n                                        alpha=.9, animated=True)\n\n        self.canvas = self.ax.figure.canvas\n        self.background = None\n        self.cnt = 0\n        self.distract = True\n        self.res = 100.0\n        self.on = False\n        self.inst = True    # show instructions from the beginning\n        self.pads = [Pad(pA, pad_a_x, pad_a_y),\n                     Pad(pB, pad_b_x, pad_b_y, 'r')]\n        self.pucks = []\n        self.i = self.ax.annotate(instructions, (.5, 0.5),\n                                  name='monospace',\n                                  verticalalignment='center',\n                                  horizontalalignment='center',\n                                  multialignment='left',\n                                  xycoords='axes fraction',\n                                  animated=False)\n        self.canvas.mpl_connect('key_press_event', self.on_key_press)\n\n    def draw(self):\n        draw_artist = self.ax.draw_artist\n        if self.background is None:\n            self.background = self.canvas.copy_from_bbox(self.ax.bbox)\n\n        # restore the clean slate background\n        self.canvas.restore_region(self.background)\n\n        # show the distractors\n        if self.distract:\n            self.line.set_ydata(np.sin(self.x + self.cnt/self.res))\n            self.line2.set_ydata(np.cos(self.x - self.cnt/self.res))\n            self.line3.set_ydata(np.tan(self.x + self.cnt/self.res))\n            self.line4.set_ydata(np.tan(self.x - self.cnt/self.res))\n            draw_artist(self.line)\n            draw_artist(self.line2)\n            draw_artist(self.line3)\n            draw_artist(self.line4)\n\n        # pucks and pads\n        if self.on:\n            self.ax.draw_artist(self.centerline)\n            for pad in self.pads:\n                pad.disp.set_y(pad.y)\n                pad.disp.set_x(pad.x)\n                self.ax.draw_artist(pad.disp)\n\n            for puck in self.pucks:\n                if puck.update(self.pads):\n                    # we only get here if someone scored\n                    self.pads[0].disp.set_label(f\"   {self.pads[0].score}\")\n                    self.pads[1].disp.set_label(f\"   {self.pads[1].score}\")\n                    self.ax.legend(loc='center', framealpha=.2,\n                                   facecolor='0.5',\n                                   prop=FontProperties(size='xx-large',\n                                                       weight='bold'))\n\n                    self.background = None\n                    self.ax.figure.canvas.draw_idle()\n                    return\n                puck.disp.set_offsets([[puck.x, puck.y]])\n                self.ax.draw_artist(puck.disp)\n\n        # just redraw the Axes rectangle\n        self.canvas.blit(self.ax.bbox)\n        self.canvas.flush_events()\n        if self.cnt == 50000:\n            # just so we don't get carried away\n            print(\"...and you've been playing for too long!!!\")\n            plt.close()\n\n        self.cnt += 1\n\n    def on_key_press(self, event):\n        if event.key == '3':\n            self.res *= 5.0\n        if event.key == '4':\n            self.res /= 5.0\n\n        if event.key == 'e':\n            self.pads[0].y += .1\n            if self.pads[0].y > 1 - .3:\n                self.pads[0].y = 1 - .3\n        if event.key == 'd':\n            self.pads[0].y -= .1\n            if self.pads[0].y < -1:\n                self.pads[0].y = -1\n\n        if event.key == 'i':\n            self.pads[1].y += .1\n            if self.pads[1].y > 1 - .3:\n                self.pads[1].y = 1 - .3\n        if event.key == 'k':\n            self.pads[1].y -= .1\n            if self.pads[1].y < -1:\n                self.pads[1].y = -1\n\n        if event.key == 'a':\n            self.pucks.append(Puck(self.puckdisp,\n                                   self.pads[randint(2)],\n                                   self.ax.bbox))\n        if event.key == 'A' and len(self.pucks):\n            self.pucks.pop()\n        if event.key == ' ' and len(self.pucks):\n            self.pucks[0]._reset(self.pads[randint(2)])\n        if event.key == '1':\n            for p in self.pucks:\n                p._slower()\n        if event.key == '2':\n            for p in self.pucks:\n                p._faster()\n\n        if event.key == 'n':\n            self.distract = not self.distract\n\n        if event.key == 'g':\n            self.on = not self.on\n        if event.key == 't':\n            self.inst = not self.inst\n            self.i.set_visible(not self.i.get_visible())\n            self.background = None\n            self.canvas.draw_idle()\n        if event.key == 'q':\n            plt.close()\n\n\nfig, ax = plt.subplots()\ncanvas = ax.figure.canvas\nanimation = Game(ax)\n\n# disable the default key bindings\nif fig.canvas.manager.key_press_handler_id is not None:\n    canvas.mpl_disconnect(fig.canvas.manager.key_press_handler_id)\n\n\n# reset the blitting background on redraw\ndef on_redraw(event):\n    animation.background = None\n\n\n# bootstrap after the first draw\ndef start_anim(event):\n    canvas.mpl_disconnect(start_anim.cid)\n\n    start_anim.timer.add_callback(animation.draw)\n    start_anim.timer.start()\n    canvas.mpl_connect('draw_event', on_redraw)\n\n\nstart_anim.cid = canvas.mpl_connect('draw_event', start_anim)\nstart_anim.timer = animation.canvas.new_timer(interval=1)\n\ntstart = time.time()\n\nplt.show()\nprint('FPS: %f' % (animation.cnt/(time.time() - tstart)))\n\n#"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        ".. tags::\n\n   interactivity: event-handling\n   purpose: fun\n\n"
      ]
    }
  ],
  "metadata": {
    "kernelspec": {
      "display_name": "Python 3",
      "language": "python",
      "name": "python3"
    },
    "language_info": {
      "codemirror_mode": {
        "name": "ipython",
        "version": 3
      },
      "file_extension": ".py",
      "mimetype": "text/x-python",
      "name": "python",
      "nbconvert_exporter": "python",
      "pygments_lexer": "ipython3",
      "version": "3.14.6"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}