Note
Go to the endto download the full example code.
Pong#
A Matplotlib based game of Pong illustrating one way to write interactiveanimations that are easily ported to multiple backends.
Note
This example exercises the interactive capabilities of Matplotlib, and thiswill not appear in the static documentation. Please run this code on yourmachine to see the interactivity.
You can copy and paste individual parts, or download the entire exampleusing the link at the bottom of the page.
importtimeimportmatplotlib.pyplotaspltimportnumpyasnpfromnumpy.randomimportrandint,randnfrommatplotlib.font_managerimportFontPropertiesinstructions="""Player A: Player B: 'e' up 'i' 'd' down 'k'press 't' -- close these instructions (animation will be much faster)press 'a' -- add a puckpress 'A' -- remove a puckpress '1' -- slow down all puckspress '2' -- speed up all puckspress '3' -- slow down distractorspress '4' -- speed up distractorspress ' ' -- reset the first puckpress 'n' -- toggle distractors on/offpress 'g' -- toggle the game on/off """classPad:def__init__(self,disp,x,y,type='l'):self.disp=dispself.x=xself.y=yself.w=.3self.score=0self.xoffset=0.3self.yoffset=0.1iftype=='r':self.xoffset*=-1.0iftype=='l'ortype=='r':self.signx=-1.0self.signy=1.0else:self.signx=1.0self.signy=-1.0defcontains(self,loc):returnself.disp.get_bbox().contains(loc.x,loc.y)classPuck:def__init__(self,disp,pad,field):self.vmax=.2self.disp=dispself.field=fieldself._reset(pad)def_reset(self,pad):self.x=pad.x+pad.xoffsetifpad.y<0:self.y=pad.y+pad.yoffsetelse:self.y=pad.y-pad.yoffsetself.vx=pad.x-self.xself.vy=pad.y+pad.w/2-self.yself._speedlimit()self._slower()self._slower()defupdate(self,pads):self.x+=self.vxself.y+=self.vyforpadinpads:ifpad.contains(self):self.vx*=1.2*pad.signxself.vy*=1.2*pad.signyfudge=.001# probably cleaner with something like...ifself.x<fudge:pads[1].score+=1self._reset(pads[0])returnTrueifself.x>7-fudge:pads[0].score+=1self._reset(pads[1])returnTrueifself.y<-1+fudgeorself.y>1-fudge:self.vy*=-1.0# add some randomness, just to make it interestingself.vy-=(randn()/300.0+1/300.0)*np.sign(self.vy)self._speedlimit()returnFalsedef_slower(self):self.vx/=5.0self.vy/=5.0def_faster(self):self.vx*=5.0self.vy*=5.0def_speedlimit(self):ifself.vx>self.vmax:self.vx=self.vmaxifself.vx<-self.vmax:self.vx=-self.vmaxifself.vy>self.vmax:self.vy=self.vmaxifself.vy<-self.vmax:self.vy=-self.vmaxclassGame:def__init__(self,ax):# create the initial lineself.ax=axax.xaxis.set_visible(False)ax.set_xlim(0,7)ax.yaxis.set_visible(False)ax.set_ylim(-1,1)pad_a_x=0pad_b_x=.50pad_a_y=pad_b_y=.30pad_b_x+=6.3# padspA,=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)# distractorsself.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 lineself.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.canvasself.background=Noneself.cnt=0self.distract=Trueself.res=100.0self.on=Falseself.inst=True# show instructions from the beginningself.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',xycoords='axes fraction',animated=False)self.canvas.mpl_connect('key_press_event',self.on_key_press)defdraw(self):draw_artist=self.ax.draw_artistifself.backgroundisNone:self.background=self.canvas.copy_from_bbox(self.ax.bbox)# restore the clean slate backgroundself.canvas.restore_region(self.background)# show the distractorsifself.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 padsifself.on:self.ax.draw_artist(self.centerline)forpadinself.pads:pad.disp.set_y(pad.y)pad.disp.set_x(pad.x)self.ax.draw_artist(pad.disp)forpuckinself.pucks:ifpuck.update(self.pads):# we only get here if someone scoredself.pads[0].disp.set_label(f"{self.pads[0].score}")self.pads[1].disp.set_label(f"{self.pads[1].score}")self.ax.legend(loc='center',framealpha=.2,facecolor='0.5',prop=FontProperties(size='xx-large',weight='bold'))self.background=Noneself.ax.figure.canvas.draw_idle()returnpuck.disp.set_offsets([[puck.x,puck.y]])self.ax.draw_artist(puck.disp)# just redraw the Axes rectangleself.canvas.blit(self.ax.bbox)self.canvas.flush_events()ifself.cnt==50000:# just so we don't get carried awayprint("...and you've been playing for too long!!!")plt.close()self.cnt+=1defon_key_press(self,event):ifevent.key=='3':self.res*=5.0ifevent.key=='4':self.res/=5.0ifevent.key=='e':self.pads[0].y+=.1ifself.pads[0].y>1-.3:self.pads[0].y=1-.3ifevent.key=='d':self.pads[0].y-=.1ifself.pads[0].y<-1:self.pads[0].y=-1ifevent.key=='i':self.pads[1].y+=.1ifself.pads[1].y>1-.3:self.pads[1].y=1-.3ifevent.key=='k':self.pads[1].y-=.1ifself.pads[1].y<-1:self.pads[1].y=-1ifevent.key=='a':self.pucks.append(Puck(self.puckdisp,self.pads[randint(2)],self.ax.bbox))ifevent.key=='A'andlen(self.pucks):self.pucks.pop()ifevent.key==' 'andlen(self.pucks):self.pucks[0]._reset(self.pads[randint(2)])ifevent.key=='1':forpinself.pucks:p._slower()ifevent.key=='2':forpinself.pucks:p._faster()ifevent.key=='n':self.distract=notself.distractifevent.key=='g':self.on=notself.onifevent.key=='t':self.inst=notself.instself.i.set_visible(notself.i.get_visible())self.background=Noneself.canvas.draw_idle()ifevent.key=='q':plt.close()fig,ax=plt.subplots()canvas=ax.figure.canvasanimation=Game(ax)# disable the default key bindingsiffig.canvas.manager.key_press_handler_idisnotNone:canvas.mpl_disconnect(fig.canvas.manager.key_press_handler_id)# reset the blitting background on redrawdefon_redraw(event):animation.background=None# bootstrap after the first drawdefstart_anim(event):canvas.mpl_disconnect(start_anim.cid)start_anim.timer.add_callback(animation.draw)start_anim.timer.start()canvas.mpl_connect('draw_event',on_redraw)start_anim.cid=canvas.mpl_connect('draw_event',start_anim)start_anim.timer=animation.canvas.new_timer(interval=1)tstart=time.time()plt.show()print('FPS:%f'%(animation.cnt/(time.time()-tstart)))#
Tags:interactivity: event-handlingpurpose: fun