game.py 7.31 KB
#!/usr/bin/env pypy
import sys

import sdl2
import sdl2.ext
import zmq


ball_speed = 9


class SoftwareRenderer(sdl2.ext.SoftwareSpriteRenderSystem):
    def render(self, components):
        sdl2.ext.fill(self.surface, sdl2.ext.Color(0, 0, 0))
        super(SoftwareRenderer, self).render(components)


class MovementSystem(sdl2.ext.Applicator):
    def __init__(self, min_x, min_y, max_x, max_y):
        super(MovementSystem, self).__init__()
        self.componenttypes = Velocity, sdl2.ext.Sprite
        self.min_x = min_x
        self.min_y = min_y
        self.max_x = max_x
        self.max_y = max_y

    def process(self, world, component_sets):
        for velocity, sprite in component_sets:
            swidth, sheight = sprite.size
            sprite.x += velocity.vx
            sprite.y += velocity.vy

            sprite.x = max(self.min_x, sprite.x)
            sprite.y = max(self.min_y, sprite.y)

            pmax_x = sprite.x + swidth
            pmax_y = sprite.y + sheight
            if pmax_x > self.max_x:
                sprite.x = self.max_x - swidth
            if pmax_y > self.max_y:
                sprite.y = self.max_y - sheight


class CollisionSystem(MovementSystem):
    def __init__(self, *args, **kwargs):
        super(CollisionSystem, self).__init__(*args)
        self.ball = kwargs["ball"]

    def _overlap(self, item):
        pos, sprite = item
        if sprite == self.ball.sprite:
            return False

        left, top, right, bottom = sprite.area
        bleft, btop, bright, bbottom = self.ball.sprite.area

        return (bleft < right and bright > left and
                btop < bottom and bbottom > top)

    def process(self, world, componentsets):
        collitems = [comp for comp in componentsets if self._overlap(comp)]
        if collitems:
            self.ball.velocity.vx = -self.ball.velocity.vx

            sprite = collitems[0][1]
            ball_center_y = self.ball.sprite.y + self.ball.sprite.size[1] // 2
            half_height = sprite.size[1] // 2
            step_size = half_height // 10
            degrees = 0.7
            paddle_center_y = sprite.y + half_height

            if ball_center_y < paddle_center_y:
                factor = (paddle_center_y - ball_center_y) // step_size
                self.ball.velocity.vy = -int(round(factor * degrees))
            elif ball_center_y > paddle_center_y:
                factor = (ball_center_y - paddle_center_y) // step_size
                self.ball.velocity.vy = int(round(factor * degrees))
            else:
                self.ball.velocity.vy = -self.ball.velocity.vy

        # Boundary collisions
        if (self.ball.sprite.y <= self.min_y or
            self.ball.sprite.y + self.ball.sprite.size[1] >= self.max_y):
            self.ball.velocity.vy = -self.ball.velocity.vy

        # Out of bounds, score a point
        if self.ball.sprite.x <= self.min_x:
            self.ball.velocity.vx = -ball_speed
            self.ball.velocity.vy = 0
            self.ball.reset_position()

        if self.ball.sprite.x + self.ball.sprite.size[0] >= self.max_x:
            self.ball.velocity.vx = ball_speed
            self.ball.velocity.vy = 0
            self.ball.reset_position()


class Velocity(object):
    def __init__(self):
        super(Velocity, self).__init__()
        self.vx = 0
        self.vy = 0


class VelocityEntity(sdl2.ext.Entity):
    def __init__(self, world, sprite, x = 0, y = 0):
        self.sprite = sprite
        self.sprite.position = x, y
        self.sprite.default_position = x, y
        self.velocity = Velocity()

    def reset_position(self):
        self.sprite.position  = self.sprite.default_position


class Player(VelocityEntity):
    pass


class Ball(VelocityEntity):
    pass


def handle_events(player, events):
    for event_type, event_key in zip(events[0::2], events[1::2]):
        if event_type == "QUIT":
            running = False
            sys.exit(0)
        elif event_type == "KEYDOWN":
            if event_key == "UP":
                player.velocity.vy = -ball_speed
            elif event_key == "DOWN":
                player.velocity.vy = ball_speed
        elif event_type == "KEYUP":
            if event_key in ("UP", "DOWN"):
                player.velocity.vy = 0

def main():
    zmq_addresses = ["tcp://127.0.0.1:8530", "tcp://127.0.0.1:8531"]
    us = int(sys.argv[1])
    zmq_context = zmq.Context.instance()

    in_sock = zmq_context.socket(zmq.PULL)
    in_sock.bind(zmq_addresses[us])
    out_sock = zmq_context.socket(zmq.PUSH)
    out_sock.connect(zmq_addresses[(us + 1) % 2])

    sdl2.ext.init()

    window = sdl2.ext.Window("Multipong Example", size=(1280, 720))
    window.show()

    world = sdl2.ext.World()

    field_size = (0, 0, window.size[0], window.size[1])
    movement = MovementSystem(*field_size)
    sprite_renderer = SoftwareRenderer(window)

    world.add_system(movement)
    world.add_system(sprite_renderer)

    factory = sdl2.ext.SpriteFactory(sdl2.ext.SOFTWARE)
    ball_sprite = factory.from_color(sdl2.ext.Color(255, 255, 255),
                                     size=(20,20))

    # Initialize each player
    players = list()
    for n in range(2):
        sprite = factory.from_color(sdl2.ext.Color(200, 200, 255),
                                    size=(20,100))
        player = Player(world, sprite,
                        n * (window.size[0] - 20), # Default side
                        window.size[1] / 2 - 50)
        players.append(player)

    ball = Ball(world, ball_sprite,
                window.size[0] / 2 - 10,
                window.size[1] / 2 - 10)
    ball.velocity.vx = -ball_speed

    # Since it affects the ball, we add this system after the ball
    collision = CollisionSystem(*field_size, ball=ball)
    world.add_system(collision)

    running = True
    step = 0
    last_ticks = 0
    ticks = sdl2.SDL_GetTicks()
    while running:
        local_events = sdl2.ext.get_events()
        send_events = [str(step)]
        # First list events to forward in (type, key) pairs
        for event in local_events:
            if event.type == sdl2.SDL_QUIT:
                send_events.extend(["QUIT", ""])
            elif event.type == sdl2.SDL_KEYDOWN:
                send_events.append("KEYDOWN")
                if event.key.keysym.sym == sdl2.SDLK_UP:
                    send_events.append("UP")
                elif event.key.keysym.sym == sdl2.SDLK_DOWN:
                    send_events.append("DOWN")
            elif event.type == sdl2.SDL_KEYUP:
                send_events.append("KEYUP")
                if event.key.keysym.sym == sdl2.SDLK_UP:
                    send_events.append("UP")
                elif event.key.keysym.sym == sdl2.SDLK_DOWN:
                    send_events.append("DOWN")
        out_sock.send_multipart(send_events)

        remote_events = in_sock.recv_multipart()
        # If steps get out of sync, continuing won't make sense
        assert remote_events[0] == str(step)
        # Handle local or remote events the same way, just vary the player
        handle_events(players[not us], remote_events[1:])
        handle_events(players[us], send_events[1:])
        world.process()
        step += 1
        # Try to maintain 30 FPS
        ticks_diff = ticks - last_ticks
        rate = 1000 / 30
        if ticks_diff < rate:
            sdl2.SDL_Delay(rate - ticks_diff)
        last_ticks = ticks

if __name__ == "__main__":
    main()