Classes for running simulations of the classic game Pong

The Class GameOfPong contains all necessary functionality for running simple simulations of Pong games.

See Also

Original implementation

References

Authors:

T Wunderlich, Electronic Vision(s), J Gille

import numpy as np

LEFT_SCORE = -1
RIGHT_SCORE = +1
GAME_CONTINUES = 0

MOVE_DOWN = -1
MOVE_UP = +1
DONT_MOVE = 0


class GameObject:
    def __init__(self, game, x_pos=0.5, y_pos=0.5, velocity=0.2,
                 direction=(0, 0)):
        """Base class for Ball and Paddle that contains basic functionality for
        an object inside a game.

        Args:
            game (GameOfPong): Instance of Pong game.
            x_pos (float, optional): Initial x position. Defaults to 0.5.
            y_pos (float, optional): Initial y position. Defaults to 0.5.
            velocity (float, optional): Change in position per iteration.
            Defaults to 0.2.
            direction (list, optional): direction vector. Defaults to [0,0].
        """
        self.x_pos = x_pos
        self.y_pos = y_pos
        self.velocity = velocity
        self.direction = direction
        self.game = game
        self.update_cell()

    def get_cell(self):
        return self.cell

    def get_pos(self):
        return self.x_pos, self.y_pos

    def update_cell(self):
        """Updates the cell in the game grid based on position.
        """
        x_cell = int(np.floor(
            (self.x_pos / self.game.x_length) * self.game.x_grid))
        y_cell = int(np.floor(
            (self.y_pos / self.game.y_length) * self.game.y_grid))
        self.cell = [x_cell, y_cell]


class Ball(GameObject):
    """Class representing the ball.

        Args:
            radius (float): Radius of ball in unit length.

        For other args, see :class:`GameObject`.
    """

    def __init__(self, game, x_pos=0.8, y_pos=0.5, velocity=0.025,
                 direction=(-1 / 2., 1 / 2.), radius=0.025):
        super().__init__(game, x_pos, y_pos, velocity, direction)
        self.ball_radius = radius  # Unit length
        self.update_cell()


class Paddle(GameObject):
    """Class representing the paddles on either end of the playing field.

        Args:
            direction (int, optional): Either -1, 0, or 1 for downward, neutral
            or upwards motion, respectively. Defaults to 0.
            left (boolean): If True, paddle is placed on the left side of the
            board, otherwise on the right side.

        For other args, see :class:`GameObject`.
    """
    length = 0.2  # Paddle length in the scale of GameOfPong.y_length

    def __init__(self, game, left, y_pos=0.5, velocity=0.05, direction=0):
        x_pos = 0. if left else game.x_length
        super().__init__(game, x_pos, y_pos, velocity,
                         direction)
        self.update_cell()

    def move_up(self):
        self.direction = MOVE_UP

    def move_down(self):
        self.direction = MOVE_DOWN

    def dont_move(self):
        self.direction = DONT_MOVE


class GameOfPong(object):
    """Class representing a game of Pong. Playing field is 1.6 by 1.0 units
    in size, discretized into x_grid*y_grid cells.
    """
    x_grid = 32
    y_grid = 20
    x_length = 1.6
    y_length = 1.0

    def __init__(self):
        self.r_paddle = Paddle(self, False)
        self.l_paddle = Paddle(self, True)

        self.reset_ball()
        self.result = 0

    def reset_ball(self, towards_left=False):
        """Resets the ball position to the center of the field after a goal.

        Args:
            towards_left (bool, optional): if True, ball direction is
            initialized towards the left side of the field, otherwise towards
            the right. Defaults to False.
        """
        initial_vx = 0.5 + 0.5 * np.random.random()
        initial_vy = 1. - initial_vx
        if towards_left:
            initial_vx *= -1
        initial_vy *= np.random.choice([-1., 1.])

        self.ball = Ball(self, direction=[initial_vx, initial_vy])
        self.ball.y_pos = np.random.random() * self.y_length

    def update_ball_direction(self):
        """In case of a collision, updates the direction of the ball. Also
        determines if the ball is in either player's net.

        Returns:
            Either GAME_CONTINUES, LEFT_SCORE or RIGHT_SCORE depending on ball
            and paddle position.
        """
        if self.ball.y_pos + self.ball.ball_radius >= self.y_length:
            # Ball on upper edge
            self.ball.direction[1] = -1 * abs(self.ball.direction[1])
        elif self.ball.y_pos - self.ball.ball_radius <= 0:
            # Ball on lower edge
            self.ball.direction[1] = abs(self.ball.direction[1])

        if self.ball.x_pos - self.ball.ball_radius <= 0:
            # Ball on left edge
            if abs(self.l_paddle.y_pos - self.ball.y_pos) <= Paddle.length / 2:
                # Ball hits left paddle
                self.ball.direction[0] = abs(self.ball.direction[0])
            else:
                return RIGHT_SCORE
        elif self.ball.x_pos + self.ball.ball_radius >= self.x_length:
            # Ball on right edge
            if abs(self.r_paddle.y_pos - self.ball.y_pos) <= Paddle.length / 2:
                # Ball hits right paddle
                self.ball.direction[0] = -1 * abs(self.ball.direction[0])
            else:
                return LEFT_SCORE
        return GAME_CONTINUES

    def propagate_ball_and_paddles(self):
        """Updates ball and paddle coordinates based on direction and velocity.
        """
        for paddle in [self.r_paddle, self.l_paddle]:
            paddle.y_pos += paddle.direction * paddle.velocity
            if paddle.y_pos < 0:
                paddle.y_pos = 0
            if paddle.y_pos > self.y_length:
                paddle.y_pos = self.y_length
            paddle.update_cell()
        self.ball.y_pos += self.ball.velocity * self.ball.direction[1]
        self.ball.x_pos += self.ball.velocity * self.ball.direction[0]
        self.ball.update_cell()

    def get_ball_cell(self):
        return self.ball.get_cell()

    def step(self):
        """Performs one game step by handling collisions, propagating all game
        objects and returning the new game state.

        Returns:
            Either GAME_CONTINUES, LEFT_SCORE or RIGHT_SCORE depending on ball
            and paddle position. see update_ball_direction()
        """
        ball_status = self.update_ball_direction()
        self.propagate_ball_and_paddles()
        self.result = ball_status
        return ball_status

Total running time of the script: ( 0 minutes 0.000 seconds)

Gallery generated by Sphinx-Gallery