Ce que vous allez apprendre

Dans ce chapitre, nous ajoutons un mode 2 joueurs en reseau via localhost. Un programme lance avec --server affiche la raquette du bas, l'autre avec --client la raquette du haut. La balle rebondit entre les deux joueurs.

Le concept

Le reseau utilise le module socket de Python avec le protocole TCP. Le serveur est autoritaire sur la balle : il calcule sa position et l'envoie au client. Chaque joueur envoie la position de sa raquette.

Le format de paquet est simple : 7 valeurs numeriques (position raquette, position et vitesse de la balle, scores) emballees avec struct.pack.

Etape par etape

1. La classe NetworkPeer

La classe gere la connexion de maniere non-bloquante. Le serveur ecoute et accepte un client. Le client se connecte au serveur. Les deux envoient et recoivent des paquets.

python
PACKET_FMT = "!fffffii"  # paddle_x, ball_x, ball_y,
                         # ball_vx, ball_vy, score1, score2
PACKET_SIZE = struct.calcsize(PACKET_FMT)

# Envoyer
pkt = struct.pack(PACKET_FMT, paddle_x, ball_x,
                  ball_y, ball_vx, ball_vy,
                  score1, score2)
sock.sendall(pkt)

# Recevoir
data = sock.recv(4096)
vals = struct.unpack(PACKET_FMT, data[:PACKET_SIZE])

2. Architecture serveur/client

Le serveur est autoritaire : il execute la physique de la balle et envoie les resultats. Le client affiche les donnees recues et envoie seulement la position de sa raquette.

3. Mode non-bloquant

Les sockets sont en mode non-bloquant avec select.select() pour ne jamais bloquer la boucle de jeu. L'attente de connexion se fait aussi de maniere non-bloquante.

4. Score et fin de partie

Quand la balle depasse le bas ou le haut de l'ecran, le joueur adverse marque un point. Le premier a 5 points gagne la partie.

Lancement

Ouvrez deux terminaux et lancez :

python
python main.py --server    # Terminal 1
python main.py --client    # Terminal 2

Points cles a retenir

Astuce : en mode non-bloquant, gerez toujours les exceptions BlockingIOError et les deconnexions.

Piege courant : oublier de gerer les paquets partiels. Le buffer de reception peut contenir un paquet incomplet qu'il faut accumuler.

Code complet

python
"""Brick Breaker - Chapter 20: Networking (2-Player over localhost)

Usage:
    python main.py --server      # Start as server (bottom paddle)
    python main.py --client      # Start as client (top paddle)
    python main.py               # Single-player mode (default)

In 2-player mode, one ball bounces between the two paddles.
Each player tries to prevent the ball from passing their side.
"""
import pygame
import sys
import math
import array
import random
import socket
import select
import struct
import threading

pygame.init()
pygame.mixer.init(frequency=44100, size=-16, channels=1, buffer=512)

GAME_WIDTH = 800
GAME_HEIGHT = 600
FPS = 60
BG_COLOR = (20, 20, 40)

PADDLE_WIDTH = 100
PADDLE_HEIGHT = 15
PADDLE_SPEED = 8

BALL_RADIUS = 8
BALL_BASE_SPEED = 300
BALL_SPEED_INCREMENT = 5

MAX_SCORE = 5  # first to 5 wins in 2-player mode

NET_PORT = 5555
NET_HOST = "127.0.0.1"

# Packet format: "!fffffii" = paddle_x(f), ball_x(f), ball_y(f),
#                              ball_vx(f), ball_vy(f), score1(i), score2(i)
PACKET_FMT = "!fffffii"
PACKET_SIZE = struct.calcsize(PACKET_FMT)

# Animation constants
TRAIL_LENGTH = 8
TRAIL_INTERVAL = 0.016

# Game states
STATE_WAITING = 0
STATE_PLAYING = 1
STATE_GAME_OVER = 2


# -- Sound generation -------------------------------------------------------

def generate_beep(frequency=440, duration_ms=100, volume=0.3):
    sample_rate = 44100
    n_samples = int(sample_rate * duration_ms / 1000)
    buf = array.array("h", [0] * n_samples)
    max_amp = int(32767 * volume)
    for i in range(n_samples):
        t = i / sample_rate
        value = int(max_amp * math.sin(2 * math.pi * frequency * t))
        fade_samples = min(500, n_samples // 4)
        if i >= n_samples - fade_samples:
            value = int(value * (n_samples - i) / fade_samples)
        buf[i] = value
    return pygame.mixer.Sound(buffer=buf)


snd_paddle = generate_beep(frequency=500, duration_ms=60, volume=0.3)
snd_wall = generate_beep(frequency=300, duration_ms=50, volume=0.2)
snd_score = generate_beep(frequency=150, duration_ms=300, volume=0.4)
snd_win = generate_beep(frequency=880, duration_ms=400, volume=0.4)

sound_muted = False
master_volume = 0.5


def play_sound(sound):
    if not sound_muted:
        sound.set_volume(master_volume)
        sound.play()


# -- BallTrail animation ---------------------------------------------------

class BallTrail:
    def __init__(self):
        self.positions = []
        self.timer = 0.0

    def update(self, dt, bx, by):
        self.timer += dt
        if self.timer >= TRAIL_INTERVAL:
            self.timer -= TRAIL_INTERVAL
            self.positions.append((bx, by))
            if len(self.positions) > TRAIL_LENGTH:
                self.positions.pop(0)

    def draw(self, surface):
        count = len(self.positions)
        for i, (x, y) in enumerate(self.positions):
            ratio = (i + 1) / count if count > 0 else 0
            alpha = int(80 * ratio)
            radius = max(1, int(BALL_RADIUS * ratio * 0.8))
            ts = pygame.Surface((radius * 2, radius * 2), pygame.SRCALPHA)
            pygame.draw.circle(ts, (100, 100, 255, alpha),
                               (radius, radius), radius)
            surface.blit(ts, (int(x) - radius, int(y) - radius))

    def reset(self):
        self.positions.clear()
        self.timer = 0.0


# -- Network helper ---------------------------------------------------------

class NetworkPeer:
    """Non-blocking network peer for 2-player communication.

    The server listens and accepts one client.
    The client connects to the server.
    Both send/receive paddle positions and game state.
    """

    def __init__(self, is_server):
        self.is_server = is_server
        self.connected = False
        self.sock = None
        self.peer_sock = None
        self.recv_buffer = b""
        # Latest received data
        self.remote_paddle_x = GAME_WIDTH // 2
        self.remote_ball_x = GAME_WIDTH // 2
        self.remote_ball_y = GAME_HEIGHT // 2
        self.remote_ball_vx = 0.0
        self.remote_ball_vy = 0.0
        self.remote_score1 = 0
        self.remote_score2 = 0

    def start(self):
        """Begin listening (server) or connecting (client) in background."""
        if self.is_server:
            self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
            self.sock.bind((NET_HOST, NET_PORT))
            self.sock.listen(1)
            self.sock.setblocking(False)
        else:
            self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            self.sock.setblocking(False)
            try:
                self.sock.connect((NET_HOST, NET_PORT))
            except BlockingIOError:
                pass  # connection in progress

    def update(self):
        """Non-blocking check for new connections or data."""
        if not self.connected:
            if self.is_server:
                # Try to accept
                try:
                    ready, _, _ = select.select([self.sock], [], [], 0)
                    if ready:
                        self.peer_sock, _ = self.sock.accept()
                        self.peer_sock.setblocking(False)
                        self.connected = True
                except Exception:
                    pass
            else:
                # Check if connection completed
                try:
                    _, writable, _ = select.select([], [self.sock], [], 0)
                    if writable:
                        err = self.sock.getsockopt(socket.SOL_SOCKET,
                                                   socket.SO_ERROR)
                        if err == 0:
                            self.peer_sock = self.sock
                            self.peer_sock.setblocking(False)
                            self.connected = True
                except Exception:
                    pass
            return

        # Read available data
        try:
            ready, _, _ = select.select([self.peer_sock], [], [], 0)
            if ready:
                data = self.peer_sock.recv(4096)
                if not data:
                    self.connected = False
                    return
                self.recv_buffer += data
                # Process complete packets
                while len(self.recv_buffer) >= PACKET_SIZE:
                    pkt = self.recv_buffer[:PACKET_SIZE]
                    self.recv_buffer = self.recv_buffer[PACKET_SIZE:]
                    vals = struct.unpack(PACKET_FMT, pkt)
                    (self.remote_paddle_x, self.remote_ball_x,
                     self.remote_ball_y, self.remote_ball_vx,
                     self.remote_ball_vy, self.remote_score1,
                     self.remote_score2) = vals
        except Exception:
            pass

    def send_state(self, paddle_x, ball_x, ball_y, ball_vx, ball_vy,
                   score1, score2):
        if not self.connected:
            return
        pkt = struct.pack(PACKET_FMT, paddle_x, ball_x, ball_y,
                          ball_vx, ball_vy, score1, score2)
        try:
            self.peer_sock.sendall(pkt)
        except Exception:
            self.connected = False

    def close(self):
        try:
            if self.peer_sock:
                self.peer_sock.close()
            if self.sock and self.sock != self.peer_sock:
                self.sock.close()
        except Exception:
            pass


# -- Game classes -----------------------------------------------------------

class NetPaddle:
    """A paddle for the 2-player mode (not a sprite for simplicity)."""

    def __init__(self, y, is_local=True):
        self.x = float(GAME_WIDTH // 2)
        self.y = y
        self.width = PADDLE_WIDTH
        self.height = PADDLE_HEIGHT
        self.is_local = is_local

    @property
    def rect(self):
        return pygame.Rect(int(self.x) - self.width // 2,
                           int(self.y),
                           self.width, self.height)

    def update_local(self, mouse_x=None):
        keys = pygame.key.get_pressed()
        if keys[pygame.K_LEFT] or keys[pygame.K_a]:
            self.x -= PADDLE_SPEED
        if keys[pygame.K_RIGHT] or keys[pygame.K_d]:
            self.x += PADDLE_SPEED
        self.x = max(self.width // 2, min(GAME_WIDTH - self.width // 2,
                                          self.x))

    def draw(self, surface, color=(220, 220, 220)):
        r = self.rect
        pygame.draw.rect(surface, color, r)
        # Highlight
        pygame.draw.line(surface, (255, 255, 255),
                         (r.left + 2, r.top + 1), (r.right - 2, r.top + 1))


class NetBall:
    """Ball for 2-player mode."""

    def __init__(self):
        self.x = float(GAME_WIDTH // 2)
        self.y = float(GAME_HEIGHT // 2)
        self.vx = BALL_BASE_SPEED * 0.7
        self.vy = BALL_BASE_SPEED * 0.7
        self.speed = BALL_BASE_SPEED

    def reset(self, direction=1):
        """Reset ball. direction: 1 = downward, -1 = upward."""
        self.x = float(GAME_WIDTH // 2)
        self.y = float(GAME_HEIGHT // 2)
        self.speed = BALL_BASE_SPEED
        angle = math.radians(60 * direction)
        self.vx = self.speed * math.cos(angle)
        self.vy = self.speed * math.sin(angle)

    def update(self, dt):
        self.x += self.vx * dt
        self.y += self.vy * dt
        # Wall bounces (left, right only)
        if self.x - BALL_RADIUS <= 0:
            self.x = BALL_RADIUS
            self.vx = abs(self.vx)
            play_sound(snd_wall)
        if self.x + BALL_RADIUS >= GAME_WIDTH:
            self.x = GAME_WIDTH - BALL_RADIUS
            self.vx = -abs(self.vx)
            play_sound(snd_wall)

    def bounce_paddle(self, paddle_rect, going_down):
        """Check collision with a paddle. going_down: True if checking
        bottom paddle, False for top paddle."""
        ball_rect = pygame.Rect(int(self.x) - BALL_RADIUS,
                                int(self.y) - BALL_RADIUS,
                                BALL_RADIUS * 2, BALL_RADIUS * 2)
        if not ball_rect.colliderect(paddle_rect):
            return False
        if going_down and self.vy > 0:
            self.y = paddle_rect.top - BALL_RADIUS
            hit_pos = (self.x - paddle_rect.left) / PADDLE_WIDTH
            angle = 150 - hit_pos * 120
            rad = math.radians(angle)
            self.speed += BALL_SPEED_INCREMENT
            self.vx = self.speed * math.cos(rad)
            self.vy = -abs(self.speed * math.sin(rad))
            return True
        if not going_down and self.vy < 0:
            self.y = paddle_rect.bottom + BALL_RADIUS
            hit_pos = (self.x - paddle_rect.left) / PADDLE_WIDTH
            angle = 150 - hit_pos * 120
            rad = math.radians(angle)
            self.speed += BALL_SPEED_INCREMENT
            self.vx = self.speed * math.cos(rad)
            self.vy = abs(self.speed * math.sin(rad))
            return True
        return False

    def draw(self, surface):
        pygame.draw.circle(surface, (255, 255, 255),
                           (int(self.x), int(self.y)), BALL_RADIUS)
        # Glow
        gs = pygame.Surface((BALL_RADIUS * 4, BALL_RADIUS * 4),
                            pygame.SRCALPHA)
        pygame.draw.circle(gs, (100, 100, 255, 80),
                           (BALL_RADIUS * 2, BALL_RADIUS * 2),
                           BALL_RADIUS * 2)
        surface.blit(gs, (int(self.x) - BALL_RADIUS * 2,
                          int(self.y) - BALL_RADIUS * 2))


# -- Parse command-line arguments -------------------------------------------

mode = "single"  # default: single-player
for arg in sys.argv[1:]:
    if arg == "--server":
        mode = "server"
    elif arg == "--client":
        mode = "client"


# -- Game setup -------------------------------------------------------------

screen = pygame.display.set_mode((GAME_WIDTH, GAME_HEIGHT))
if mode == "single":
    pygame.display.set_caption("Brick Breaker - Chapter 20 (Single Player)")
elif mode == "server":
    pygame.display.set_caption("Brick Breaker - Ch20 (Server - Bottom)")
else:
    pygame.display.set_caption("Brick Breaker - Ch20 (Client - Top)")

clock = pygame.time.Clock()
hud_font = pygame.font.SysFont("monospace", 18)
big_font = pygame.font.SysFont("monospace", 48, bold=True)
medium_font = pygame.font.SysFont("monospace", 24)
small_font = pygame.font.SysFont("monospace", 14)

# 2-player mode objects
if mode != "single":
    net = NetworkPeer(is_server=(mode == "server"))
    net.start()

    bottom_paddle = NetPaddle(GAME_HEIGHT - 40, is_local=(mode == "server"))
    top_paddle = NetPaddle(25, is_local=(mode == "client"))
    net_ball = NetBall()
    ball_trail = BallTrail()

    score_bottom = 0  # server's score
    score_top = 0     # client's score
    net_state = STATE_WAITING
    net_ball.reset(direction=1)

# Single-player mode uses the standard brick breaker (simplified here)
else:
    # --- Single-player imports from ch19 pattern (simplified) ---
    from collections import namedtuple

    ball_trail = BallTrail()

    class SPPaddle:
        def __init__(self):
            self.x = float(GAME_WIDTH // 2)
            self.y = GAME_HEIGHT - 40
            self.width = PADDLE_WIDTH
            self.height = PADDLE_HEIGHT

        @property
        def rect(self):
            return pygame.Rect(int(self.x) - self.width // 2,
                               int(self.y),
                               self.width, self.height)

        def update(self):
            keys = pygame.key.get_pressed()
            if keys[pygame.K_LEFT] or keys[pygame.K_a]:
                self.x -= PADDLE_SPEED
            if keys[pygame.K_RIGHT] or keys[pygame.K_d]:
                self.x += PADDLE_SPEED
            self.x = max(self.width // 2,
                         min(GAME_WIDTH - self.width // 2, self.x))

        def draw(self, surface):
            r = self.rect
            pygame.draw.rect(surface, (220, 220, 220), r)
            pygame.draw.line(surface, (255, 255, 255),
                             (r.left + 2, r.top + 1),
                             (r.right - 2, r.top + 1))

    sp_paddle = SPPaddle()
    sp_ball = NetBall()
    sp_ball.reset(direction=-1)

    # Simple bricks
    sp_bricks = []
    for row in range(6):
        hue = int(360 * row / 6)
        c = pygame.Color(0)
        c.hsva = (hue, 100, 100, 100)
        color = (c.r, c.g, c.b)
        for col in range(10):
            bx = 30 + col * 75
            by = 50 + row * 25
            sp_bricks.append({"rect": pygame.Rect(bx, by, 70, 20),
                               "color": color, "alive": True})

    sp_score = 0
    sp_lives = 3
    sp_state = STATE_PLAYING


# -- Main loop --------------------------------------------------------------

running = True
while running:
    dt = clock.tick(FPS) / 1000.0

    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_ESCAPE:
                running = False
            if event.key == pygame.K_F1:
                sound_muted = not sound_muted

    if mode != "single":
        # ===== 2-PLAYER MODE =====
        net.update()

        if net_state == STATE_WAITING:
            if net.connected:
                net_state = STATE_PLAYING
                net_ball.reset(direction=1)
                score_bottom = 0
                score_top = 0

        elif net_state == STATE_PLAYING:
            # Update local paddle
            if mode == "server":
                bottom_paddle.update_local()
                top_paddle.x = net.remote_paddle_x
            else:
                top_paddle.update_local()
                bottom_paddle.x = net.remote_paddle_x

            # Server is authoritative over the ball
            if mode == "server":
                net_ball.update(dt)
                ball_trail.update(dt, net_ball.x, net_ball.y)

                # Bounce off bottom paddle (server's)
                if net_ball.bounce_paddle(bottom_paddle.rect, True):
                    play_sound(snd_paddle)
                # Bounce off top paddle (client's)
                if net_ball.bounce_paddle(top_paddle.rect, False):
                    play_sound(snd_paddle)

                # Scoring
                if net_ball.y - BALL_RADIUS > GAME_HEIGHT:
                    # Bottom missed -> top scores
                    score_top += 1
                    play_sound(snd_score)
                    net_ball.reset(direction=-1)
                    ball_trail.reset()
                elif net_ball.y + BALL_RADIUS < 0:
                    # Top missed -> bottom scores
                    score_bottom += 1
                    play_sound(snd_score)
                    net_ball.reset(direction=1)
                    ball_trail.reset()

                if score_bottom >= MAX_SCORE or score_top >= MAX_SCORE:
                    net_state = STATE_GAME_OVER

                # Send state to client
                net.send_state(bottom_paddle.x, net_ball.x, net_ball.y,
                               net_ball.vx, net_ball.vy,
                               score_bottom, score_top)

            else:
                # Client receives ball state from server
                net_ball.x = net.remote_ball_x
                net_ball.y = net.remote_ball_y
                net_ball.vx = net.remote_ball_vx
                net_ball.vy = net.remote_ball_vy
                score_bottom = net.remote_score1
                score_top = net.remote_score2
                ball_trail.update(dt, net_ball.x, net_ball.y)

                if score_bottom >= MAX_SCORE or score_top >= MAX_SCORE:
                    net_state = STATE_GAME_OVER

                # Send paddle position to server
                net.send_state(top_paddle.x, 0, 0, 0, 0, 0, 0)

            if not net.connected:
                net_state = STATE_GAME_OVER

        # -- Draw 2-player --
        screen.fill(BG_COLOR)

        if net_state == STATE_WAITING:
            wait_text = big_font.render("WAITING...", True, (255, 200, 50))
            screen.blit(wait_text,
                        (GAME_WIDTH // 2 - wait_text.get_width() // 2,
                         GAME_HEIGHT // 2 - 50))
            if mode == "server":
                info = medium_font.render(
                    "Server listening on port {}".format(NET_PORT),
                    True, (150, 150, 150))
            else:
                info = medium_font.render(
                    "Connecting to {}:{}...".format(NET_HOST, NET_PORT),
                    True, (150, 150, 150))
            screen.blit(info, (GAME_WIDTH // 2 - info.get_width() // 2,
                               GAME_HEIGHT // 2 + 20))
        else:
            # Draw centre line
            for x in range(0, GAME_WIDTH, 20):
                pygame.draw.rect(screen, (50, 50, 70),
                                 (x, GAME_HEIGHT // 2 - 1, 10, 2))
            # Paddles
            bottom_paddle.draw(screen, (100, 200, 255))
            top_paddle.draw(screen, (255, 150, 100))
            # Ball
            ball_trail.draw(screen)
            net_ball.draw(screen)
            # Scores
            bs = big_font.render(str(score_bottom), True, (100, 200, 255))
            screen.blit(bs, (GAME_WIDTH // 2 - bs.get_width() // 2,
                             GAME_HEIGHT // 2 + 30))
            ts = big_font.render(str(score_top), True, (255, 150, 100))
            screen.blit(ts, (GAME_WIDTH // 2 - ts.get_width() // 2,
                             GAME_HEIGHT // 2 - 70))
            # Labels
            bl = small_font.render("Server (bottom)", True, (100, 200, 255))
            screen.blit(bl, (10, GAME_HEIGHT - 20))
            tl = small_font.render("Client (top)", True, (255, 150, 100))
            screen.blit(tl, (10, 5))

            if net_state == STATE_GAME_OVER:
                overlay = pygame.Surface((GAME_WIDTH, GAME_HEIGHT),
                                        pygame.SRCALPHA)
                overlay.fill((0, 0, 0, 150))
                screen.blit(overlay, (0, 0))
                if not net.connected:
                    msg = "DISCONNECTED"
                    color = (255, 150, 50)
                elif score_bottom >= MAX_SCORE:
                    msg = "SERVER WINS!" if mode == "server" else "YOU LOSE!"
                    color = (100, 255, 100) if mode == "server" else (
                        255, 60, 60)
                else:
                    msg = "CLIENT WINS!" if mode == "client" else "YOU LOSE!"
                    color = (100, 255, 100) if mode == "client" else (
                        255, 60, 60)
                gt = big_font.render(msg, True, color)
                screen.blit(gt,
                            (GAME_WIDTH // 2 - gt.get_width() // 2,
                             GAME_HEIGHT // 2 - 30))
                hint = medium_font.render("Press ESC to quit", True,
                                          (200, 200, 200))
                screen.blit(hint,
                            (GAME_WIDTH // 2 - hint.get_width() // 2,
                             GAME_HEIGHT // 2 + 30))

    else:
        # ===== SINGLE-PLAYER MODE =====
        if sp_state == STATE_PLAYING:
            sp_paddle.update()
            sp_ball.update(dt)
            ball_trail.update(dt, sp_ball.x, sp_ball.y)

            # Top wall bounce
            if sp_ball.y - BALL_RADIUS <= 0:
                sp_ball.y = BALL_RADIUS
                sp_ball.vy = abs(sp_ball.vy)
                play_sound(snd_wall)

            # Paddle bounce
            if sp_ball.bounce_paddle(sp_paddle.rect, True):
                play_sound(snd_paddle)

            # Brick collision
            ball_rect = pygame.Rect(int(sp_ball.x) - BALL_RADIUS,
                                    int(sp_ball.y) - BALL_RADIUS,
                                    BALL_RADIUS * 2, BALL_RADIUS * 2)
            for brick in sp_bricks:
                if brick["alive"] and ball_rect.colliderect(brick["rect"]):
                    brick["alive"] = False
                    sp_ball.vy = -sp_ball.vy
                    sp_score += 10
                    play_sound(snd_paddle)

            # Fall off bottom
            if sp_ball.y - BALL_RADIUS > GAME_HEIGHT:
                sp_lives -= 1
                if sp_lives <= 0:
                    sp_state = STATE_GAME_OVER
                else:
                    sp_ball.reset(direction=-1)
                    ball_trail.reset()
                    play_sound(snd_score)

            # Win check
            if not any(b["alive"] for b in sp_bricks):
                sp_state = STATE_GAME_OVER

        # Draw single-player
        screen.fill(BG_COLOR)
        for brick in sp_bricks:
            if brick["alive"]:
                pygame.draw.rect(screen, brick["color"], brick["rect"])
                highlight = tuple(min(255, c + 50) for c in brick["color"])
                pygame.draw.line(screen, highlight,
                                 (brick["rect"].left + 1,
                                  brick["rect"].top + 1),
                                 (brick["rect"].right - 2,
                                  brick["rect"].top + 1))
        ball_trail.draw(screen)
        sp_ball.draw(screen)
        sp_paddle.draw(screen)

        # HUD
        sc = hud_font.render("Score: {}".format(sp_score), True,
                             (255, 255, 100))
        screen.blit(sc, (10, 10))
        lv = hud_font.render("Lives: {}".format(sp_lives), True,
                             (255, 100, 100))
        screen.blit(lv, (GAME_WIDTH - lv.get_width() - 10, 10))

        info = small_font.render(
            "Run with --server or --client for 2-player mode",
            True, (100, 100, 100))
        screen.blit(info, (10, GAME_HEIGHT - 20))

        if sp_state == STATE_GAME_OVER:
            overlay = pygame.Surface((GAME_WIDTH, GAME_HEIGHT),
                                    pygame.SRCALPHA)
            overlay.fill((0, 0, 0, 150))
            screen.blit(overlay, (0, 0))
            if sp_lives <= 0:
                msg = "GAME OVER"
                color = (255, 60, 60)
            else:
                msg = "YOU WIN!"
                color = (100, 255, 100)
            gt = big_font.render(msg, True, color)
            screen.blit(gt,
                        (GAME_WIDTH // 2 - gt.get_width() // 2,
                         GAME_HEIGHT // 2 - 30))

    pygame.display.flip()

# Cleanup
if mode != "single":
    net.close()
pygame.quit()
sys.exit()

Telechargement

Telecharger le code source de ce chapitre : brick-breaker-ch20.zip