Jeu Matplotlib : Pong

Beginner

This tutorial is from open-source community. Access the source code

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.