Pipong

一个基于Matplotlib的Pong游戏,说明了一种编写交互动画的方法,它很容易移植到多个后端pipong.py由Paul Ivanov撰写http://pirsquared.org

  1. import numpy as np
  2. import matplotlib.pyplot as plt
  3. from numpy.random import randn, randint
  4. from matplotlib.font_manager import FontProperties
  5. instructions = """
  6. Player A: Player B:
  7. 'e' up 'i'
  8. 'd' down 'k'
  9. press 't' -- close these instructions
  10. (animation will be much faster)
  11. press 'a' -- add a puck
  12. press 'A' -- remove a puck
  13. press '1' -- slow down all pucks
  14. press '2' -- speed up all pucks
  15. press '3' -- slow down distractors
  16. press '4' -- speed up distractors
  17. press ' ' -- reset the first puck
  18. press 'n' -- toggle distractors on/off
  19. press 'g' -- toggle the game on/off
  20. """
  21. class Pad(object):
  22. def __init__(self, disp, x, y, type='l'):
  23. self.disp = disp
  24. self.x = x
  25. self.y = y
  26. self.w = .3
  27. self.score = 0
  28. self.xoffset = 0.3
  29. self.yoffset = 0.1
  30. if type == 'r':
  31. self.xoffset *= -1.0
  32. if type == 'l' or type == 'r':
  33. self.signx = -1.0
  34. self.signy = 1.0
  35. else:
  36. self.signx = 1.0
  37. self.signy = -1.0
  38. def contains(self, loc):
  39. return self.disp.get_bbox().contains(loc.x, loc.y)
  40. class Puck(object):
  41. def __init__(self, disp, pad, field):
  42. self.vmax = .2
  43. self.disp = disp
  44. self.field = field
  45. self._reset(pad)
  46. def _reset(self, pad):
  47. self.x = pad.x + pad.xoffset
  48. if pad.y < 0:
  49. self.y = pad.y + pad.yoffset
  50. else:
  51. self.y = pad.y - pad.yoffset
  52. self.vx = pad.x - self.x
  53. self.vy = pad.y + pad.w/2 - self.y
  54. self._speedlimit()
  55. self._slower()
  56. self._slower()
  57. def update(self, pads):
  58. self.x += self.vx
  59. self.y += self.vy
  60. for pad in pads:
  61. if pad.contains(self):
  62. self.vx *= 1.2 * pad.signx
  63. self.vy *= 1.2 * pad.signy
  64. fudge = .001
  65. # probably cleaner with something like...
  66. if self.x < fudge:
  67. pads[1].score += 1
  68. self._reset(pads[0])
  69. return True
  70. if self.x > 7 - fudge:
  71. pads[0].score += 1
  72. self._reset(pads[1])
  73. return True
  74. if self.y < -1 + fudge or self.y > 1 - fudge:
  75. self.vy *= -1.0
  76. # add some randomness, just to make it interesting
  77. self.vy -= (randn()/300.0 + 1/300.0) * np.sign(self.vy)
  78. self._speedlimit()
  79. return False
  80. def _slower(self):
  81. self.vx /= 5.0
  82. self.vy /= 5.0
  83. def _faster(self):
  84. self.vx *= 5.0
  85. self.vy *= 5.0
  86. def _speedlimit(self):
  87. if self.vx > self.vmax:
  88. self.vx = self.vmax
  89. if self.vx < -self.vmax:
  90. self.vx = -self.vmax
  91. if self.vy > self.vmax:
  92. self.vy = self.vmax
  93. if self.vy < -self.vmax:
  94. self.vy = -self.vmax
  95. class Game(object):
  96. def __init__(self, ax):
  97. # create the initial line
  98. self.ax = ax
  99. ax.set_ylim([-1, 1])
  100. ax.set_xlim([0, 7])
  101. padAx = 0
  102. padBx = .50
  103. padAy = padBy = .30
  104. padBx += 6.3
  105. # pads
  106. pA, = self.ax.barh(padAy, .2,
  107. height=.3, color='k', alpha=.5, edgecolor='b',
  108. lw=2, label="Player B",
  109. animated=True)
  110. pB, = self.ax.barh(padBy, .2,
  111. height=.3, left=padBx, color='k', alpha=.5,
  112. edgecolor='r', lw=2, label="Player A",
  113. animated=True)
  114. # distractors
  115. self.x = np.arange(0, 2.22*np.pi, 0.01)
  116. self.line, = self.ax.plot(self.x, np.sin(self.x), "r",
  117. animated=True, lw=4)
  118. self.line2, = self.ax.plot(self.x, np.cos(self.x), "g",
  119. animated=True, lw=4)
  120. self.line3, = self.ax.plot(self.x, np.cos(self.x), "g",
  121. animated=True, lw=4)
  122. self.line4, = self.ax.plot(self.x, np.cos(self.x), "r",
  123. animated=True, lw=4)
  124. # center line
  125. self.centerline, = self.ax.plot([3.5, 3.5], [1, -1], 'k',
  126. alpha=.5, animated=True, lw=8)
  127. # puck (s)
  128. self.puckdisp = self.ax.scatter([1], [1], label='_nolegend_',
  129. s=200, c='g',
  130. alpha=.9, animated=True)
  131. self.canvas = self.ax.figure.canvas
  132. self.background = None
  133. self.cnt = 0
  134. self.distract = True
  135. self.res = 100.0
  136. self.on = False
  137. self.inst = True # show instructions from the beginning
  138. self.background = None
  139. self.pads = []
  140. self.pads.append(Pad(pA, padAx, padAy))
  141. self.pads.append(Pad(pB, padBx, padBy, 'r'))
  142. self.pucks = []
  143. self.i = self.ax.annotate(instructions, (.5, 0.5),
  144. name='monospace',
  145. verticalalignment='center',
  146. horizontalalignment='center',
  147. multialignment='left',
  148. textcoords='axes fraction',
  149. animated=False)
  150. self.canvas.mpl_connect('key_press_event', self.key_press)
  151. def draw(self, evt):
  152. draw_artist = self.ax.draw_artist
  153. if self.background is None:
  154. self.background = self.canvas.copy_from_bbox(self.ax.bbox)
  155. # restore the clean slate background
  156. self.canvas.restore_region(self.background)
  157. # show the distractors
  158. if self.distract:
  159. self.line.set_ydata(np.sin(self.x + self.cnt/self.res))
  160. self.line2.set_ydata(np.cos(self.x - self.cnt/self.res))
  161. self.line3.set_ydata(np.tan(self.x + self.cnt/self.res))
  162. self.line4.set_ydata(np.tan(self.x - self.cnt/self.res))
  163. draw_artist(self.line)
  164. draw_artist(self.line2)
  165. draw_artist(self.line3)
  166. draw_artist(self.line4)
  167. # pucks and pads
  168. if self.on:
  169. self.ax.draw_artist(self.centerline)
  170. for pad in self.pads:
  171. pad.disp.set_y(pad.y)
  172. pad.disp.set_x(pad.x)
  173. self.ax.draw_artist(pad.disp)
  174. for puck in self.pucks:
  175. if puck.update(self.pads):
  176. # we only get here if someone scored
  177. self.pads[0].disp.set_label(
  178. " " + str(self.pads[0].score))
  179. self.pads[1].disp.set_label(
  180. " " + str(self.pads[1].score))
  181. self.ax.legend(loc='center', framealpha=.2,
  182. facecolor='0.5',
  183. prop=FontProperties(size='xx-large',
  184. weight='bold'))
  185. self.background = None
  186. self.ax.figure.canvas.draw_idle()
  187. return True
  188. puck.disp.set_offsets([[puck.x, puck.y]])
  189. self.ax.draw_artist(puck.disp)
  190. # just redraw the axes rectangle
  191. self.canvas.blit(self.ax.bbox)
  192. self.canvas.flush_events()
  193. if self.cnt == 50000:
  194. # just so we don't get carried away
  195. print("...and you've been playing for too long!!!")
  196. plt.close()
  197. self.cnt += 1
  198. return True
  199. def key_press(self, event):
  200. if event.key == '3':
  201. self.res *= 5.0
  202. if event.key == '4':
  203. self.res /= 5.0
  204. if event.key == 'e':
  205. self.pads[0].y += .1
  206. if self.pads[0].y > 1 - .3:
  207. self.pads[0].y = 1 - .3
  208. if event.key == 'd':
  209. self.pads[0].y -= .1
  210. if self.pads[0].y < -1:
  211. self.pads[0].y = -1
  212. if event.key == 'i':
  213. self.pads[1].y += .1
  214. if self.pads[1].y > 1 - .3:
  215. self.pads[1].y = 1 - .3
  216. if event.key == 'k':
  217. self.pads[1].y -= .1
  218. if self.pads[1].y < -1:
  219. self.pads[1].y = -1
  220. if event.key == 'a':
  221. self.pucks.append(Puck(self.puckdisp,
  222. self.pads[randint(2)],
  223. self.ax.bbox))
  224. if event.key == 'A' and len(self.pucks):
  225. self.pucks.pop()
  226. if event.key == ' ' and len(self.pucks):
  227. self.pucks[0]._reset(self.pads[randint(2)])
  228. if event.key == '1':
  229. for p in self.pucks:
  230. p._slower()
  231. if event.key == '2':
  232. for p in self.pucks:
  233. p._faster()
  234. if event.key == 'n':
  235. self.distract = not self.distract
  236. if event.key == 'g':
  237. self.on = not self.on
  238. if event.key == 't':
  239. self.inst = not self.inst
  240. self.i.set_visible(not self.i.get_visible())
  241. self.background = None
  242. self.canvas.draw_idle()
  243. if event.key == 'q':
  244. plt.close()

下载这个示例