Introduction
Dans ce laboratoire, vous allez apprendre à créer un jeu interactif de Pong à l'aide de Matplotlib. Pong est un jeu d'arcade classique qui implique deux joueurs qui renvoient une balle de l'un à l'autre à travers un terrain de jeu virtuel à l'aide de palettes. Ce laboratoire vous guidera tout au long du processus de construction de votre propre jeu de Pong à l'aide de Python et de Matplotlib.
Conseils sur la VM
Une fois le démarrage de la VM terminé, cliquez dans le coin supérieur gauche pour basculer vers l'onglet Notebook pour accéder à Jupyter Notebook pour pratiquer.
Parfois, vous devrez peut-être attendre quelques secondes pour que Jupyter Notebook ait fini de charger. La validation des opérations ne peut pas être automatisée en raison des limitations de Jupyter Notebook.
Si vous rencontrez des problèmes pendant l'apprentissage, n'hésitez pas à demander à Labby. Donnez votre feedback après la session, et nous résoudrons rapidement le problème pour vous.
Install Matplotlib
Avant de commencer, nous devons nous assurer que Matplotlib est installé. Vous pouvez installer Matplotlib à l'aide de pip en exécutant la commande suivante dans votre terminal :
pip install matplotlib
Import Libraries
La première chose que nous devons faire est d'importer les bibliothèques nécessaires. Nous allons utiliser time, numpy et matplotlib.
import time
import matplotlib.pyplot as plt
import numpy as np
Définir les classes Pad et Puck
Ensuite, nous devons définir les classes Pad et Puck. La classe Pad représente les palettes utilisées par les joueurs, tandis que la classe Puck représente la balle.
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
## probablement plus propre avec quelque chose comme...
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
## ajoutez un peu de hasard, juste pour le rendre intéressant
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
Définir la classe Game
Maintenant que nous avons défini les classes Pad et Puck, nous pouvons passer à la définition de la classe Game. Cette classe sera responsable de gérer la logique du jeu et de dessiner le jeu sur l'écran.
class Game:
def __init__(self, ax):
## créer la ligne initiale
self.ax = ax
ax.xaxis.set_visible(False)
ax.set_xlim([0, 7])
ax.yaxis.set_visible(False)
ax.set_ylim([-1, 1])
pad_a_x = 0
pad_b_x =.50
pad_a_y = pad_b_y =.30
pad_b_x += 6.3
## palettes
pA, = self.ax.barh(pad_a_y,.2,
height=.3, color='k', alpha=.5, edgecolor='b',
lw=2, label="Joueur 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="Joueur A",
animated=True)
## éléments de distraction
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)
## ligne centrale
self.centerline, = self.ax.plot([3.5, 3.5], [1, -1], 'k',
alpha=.5, animated=True, lw=8)
## rondelle (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 ## afficher les instructions dès le début
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',
xycoords='axes fraction',
animated=False)
self.canvas.mpl_connect('key_press_event', self.on_key_press)
def draw(self):
draw_artist = self.ax.draw_artist
if self.background is None:
self.background = self.canvas.copy_from_bbox(self.ax.bbox)
## restaurer l'arrière-plan propre
self.canvas.restore_region(self.background)
## afficher les éléments de distraction
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)
## rondelles et palettes
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):
## nous ne sommes ici que si quelqu'un a marqué
self.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 = None
self.ax.figure.canvas.draw_idle()
return
puck.disp.set_offsets([[puck.x, puck.y]])
self.ax.draw_artist(puck.disp)
## simplement redessiner le rectangle des axes
self.canvas.blit(self.ax.bbox)
self.canvas.flush_events()
if self.cnt == 50000:
## juste pour ne pas trop s'emporter
print("...et vous avez joué trop longtemps!!!")
plt.close()
self.cnt += 1
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 == '':
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()
Remarque : Dans le code, randint(2) et FontProperties sont supposés être définis correctement ailleurs dans le contexte du programme.
Créer l'animation du jeu
Maintenant que nous avons défini la classe Game, nous pouvons créer l'animation du jeu en instanciant un objet Game et en appelant sa méthode draw() dans une boucle.
fig, ax = plt.subplots()
canvas = ax.figure.canvas
animation = Game(ax)
## désactiver les liaisons de touches par défaut
if fig.canvas.manager.key_press_handler_id is not None:
canvas.mpl_disconnect(fig.canvas.manager.key_press_handler_id)
## réinitialiser l'arrière-plan de la copie mémoire lors du redessin
def on_redraw(event):
animation.background = None
## amorcer après le premier dessin
def start_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)))
Résumé
Dans ce laboratoire, vous avez appris à créer un jeu interactif de Pong à l'aide de Matplotlib. Vous avez appris à définir les classes Pad et Puck, ainsi que la classe Game, qui est responsable de gérer la logique du jeu et de dessiner le jeu sur l'écran. Vous avez également appris à créer l'animation du jeu en instanciant un objet Game et en appelant sa méthode draw() dans une boucle. Avec ces connaissances, vous pouvez désormais créer vos propres jeux interactifs à l'aide de Python et de Matplotlib.