Search code examples
pythonpygamepython-multithreadingasyncsocketpython-sockets

how to give directions for a Pong game from outside the game loop?


I am trying to use a simple Server/Client socket to control the paddle in the game. The idea is to make the client able to control the paddle by sending directions to a server which will pass this directions to the game. so i have created to threads one to be listening for client's directions and storing them in a varibale, and the other thread is for running the game. The problem is that when the client send the lettre "z" (to move the paddle up) the game starts but he send another lettre the game crushes.

I don't know if i have explained enough the problem, I'm new in python and i'll appriciate your help to tell me how to fix the problem !

Here is my code :

import socket
import threading
from threading import RLock
import pygame
from pygame.locals import *

class Pong(object):
    def __init__(self, screensize):

        self.screensize = screensize

        self.centerx = int(screensize[0]*0.5)
        self.centery = int(screensize[1]*0.5)

        self.radius = 8

        self.rect = pygame.Rect(self.centerx-self.radius,
                            self.centery-self.radius,
                            self.radius*2, self.radius*2)

        self.color = (100,100,255)

        self.direction = [1,1]

        self.speedx = 2
        self.speedy = 5


        self.hit_edge_left = False
        self.hit_edge_right = False

    def update(self, player_paddle, ai_paddle):

        self.centerx += self.direction[0]*self.speedx
        self.centery += self.direction[1]*self.speedy

        self.rect.center = (self.centerx, self.centery)

        if self.rect.top <= 0:
            self.direction[1] = 1
        elif self.rect.bottom >= self.screensize[1]-1:
            self.direction[1] = -1

        if self.rect.right >= self.screensize[0]-1:
            self.hit_edge_right = True
        elif self.rect.left <= 0:
            self.hit_edge_left = True



        if self.rect.colliderect(player_paddle.rect):
            self.direction[0] = -1
        if self.rect.colliderect(ai_paddle.rect):
            self.direction[0] = 1

    def render(self, screen):
        pygame.draw.circle(screen, self.color, self.rect.center, self.radius, 0)
        pygame.draw.circle(screen, (0,0,0), self.rect.center, self.radius, 1)


class AIPaddle(object):
    def __init__(self, screensize):
        self.screensize = screensize

        self.centerx = 5
        self.centery = int(screensize[1]*0.5)

        self.height = 100
        self.width = 10

        self.rect = pygame.Rect(0, self.centery-int(self.height*0.5), self.width, self.height)

        self.color = (255,100,100)


        self.speed = 3

    def update(self, pong):
        if pong.rect.top < self.rect.top:
            self.centery -= self.speed
        elif pong.rect.bottom > self.rect.bottom:
            self.centery += self.speed

        self.rect.center = (self.centerx, self.centery)

    def render(self, screen):
        pygame.draw.rect(screen, self.color, self.rect, 0)
        pygame.draw.rect(screen, (0,0,0), self.rect, 1)


class PlayerPaddle(object):
    def __init__(self, screensize):
        self.screensize = screensize

        self.centerx = screensize[0]-5
        self.centery = int(screensize[1]*0.5)

        self.height = 100
        self.width = 10

        self.rect = pygame.Rect(0, self.centery-int(self.height*0.5), self.width, self.height)

        self.color = (100,255,100)

        self.speed = 3
        self.direction = 0

    def update(self):
        self.centery += self.direction*self.speed

        self.rect.center = (self.centerx, self.centery)
        if self.rect.top < 0:
            self.rect.top = 0
        if self.rect.bottom > self.screensize[1]-1:
            self.rect.bottom = self.screensize[1]-1

    def render(self, screen):
        pygame.draw.rect(screen, self.color, self.rect, 0)
        pygame.draw.rect(screen, (0,0,0), self.rect, 1)

class _Directions(object):
    data = ""

    def __init__(self):
        _Directions.data = ""

    def set_Data(self, data_Val):
        _Directions.data = data_Val

    def get_Data(self):
        return _Directions.data




def pongGame(Direct_Data):
    pygame.init()

    screensize = (640,480)

    screen = pygame.display.set_mode(screensize)

    clock = pygame.time.Clock()


    pong = Pong(screensize)
    ai_paddle = AIPaddle(screensize)
    player_paddle = PlayerPaddle(screensize)

    running = True

    while running:
        #fps limiting/reporting phase
        clock.tick(64)

        #event handling phase
        for event in pygame.event.get():
            if event.type == QUIT:
                running = False

        if(Direct_Data.get_Data() == "z"):
            player_paddle.direction = -1
        elif(Direct_Data.get_Data() == "s"):
            player_paddle.direction = 1

        #object updating phase
        ai_paddle.update(pong)
        player_paddle.update()
        pong.update(player_paddle, ai_paddle)


        if pong.hit_edge_left:
            print ("You Won")
            pong = Pong(screensize)
            ai_paddle = AIPaddle(screensize)
            player_paddle = PlayerPaddle(screensize)

        elif pong.hit_edge_right:
            print ("You Lose")
            pong = Pong(screensize)
            ai_paddle = AIPaddle(screensize)
            player_paddle = PlayerPaddle(screensize)


        #rendering phase
        screen.fill((100,100,100))

        ai_paddle.render(screen)
        player_paddle.render(screen)
        pong.render(screen)

        pygame.display.flip()

    pygame.quit()


def get_Directions(Direct_Data, client):
    data = client.recv(1024).decode()
    Direct_Data.set_Data(data)
    client.send(data.encode())


def gameStart():

    verrou = RLock()
    Direct_Data = _Directions()
    server = socket.socket()
    host = '127.0.0.1'
    port = 1234

    server.bind((host, port))
    server.listen(1)
    print ("[*] Server started !")
    client, addr = server.accept()
    print("[*] Got connection from ip: ", addr[0])
    while True:

        with verrou:

            t = threading.Thread(target=get_Directions, args=[Direct_Data, client])

            t1 = threading.Thread(target=pongGame, args=[Direct_Data])
        t.start()
        t1.start()

    client.close()


if __name__ == '__main__':
    gameStart()

Solution

  • You are waiting for 1024 bytes of data, and it doesn't arrive, so the thread hangs. Either change that to 1, if you are sending key-by-key, or use non-blocking sockets.

    You also don't use new socket to connect to the listening one. You need two of them. One is server, one is client.

    Here is what you want, a little bit simplified. It only changes the window background. You should stick with threading module if you are seriously planning a networking game. It gives you more flexibility than low-level threads. But this code is good enough to see how to do what you want. Also, there is no timestamping and synchronization. When using this on localhost and local ethernet it'll work fine.

    Code:

    
    
    from thread import start_new_thread as thread
    from time import sleep
    from collections import deque
    import pygame
    import socket
    
    running = 1
    done = 0
    evt_queue = deque() # deque() is thread safe with fast memory access of end items
    
    def Quit ():
        global running
        print "Quitting"
        running = 0
        while done!=2:
            sleep(0.001)
    
    def sender ():
        """Runs in thread and sends all valid events through socket."""
        global done
        print "Starting sender..."
        s = socket.socket()
        s.connect(("127.0.0.1", 1234))
        print "Sender connected!"
        while running:
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    s.send("Q")
                    break
                elif event.type == pygame.KEYDOWN:
                    if event.unicode:
                        # Ensure only one byte is always sent:
                        k = event.unicode.encode("UTF-8")[0].upper()
                        s.send(k)
                        if k=="Q": break
        s.close()
        done += 1
        print "Sender done!"
    
    def listener (client):
        """Thread that waits for events from client and puts them on queue."""
        global done
        print "Listening!"
        while running:
            try: e = client.recv(1) # Breaks when sender closes the connection
            except: break
            evt_queue.append(e)
        done += 1
        print "Listener done!"
    
    # Dictionary with events and their associated functions
    actions = {
        "W":
            lambda: screen.fill((255, 255, 255)),
        "K":
            lambda: screen.fill((0, 0, 0)),
        "R":
            lambda: screen.fill((255, 0, 0)),
        "G":
            lambda: screen.fill((0, 255, 0)),
        "B":
            lambda: screen.fill((0, 0, 255)),
        "Q": Quit,
        None: lambda: None}
    
    def MainLoop ():
        """Manages the game. This example only changes window colours."""
        clock = pygame.time.Clock()
        while running:
            try: e = evt_queue.popleft()
            except: e = None
            action = actions.get(e, actions[None])
            action()
            clock.tick(64)
            pygame.display.flip()
    
    pygame.init()
    screen = pygame.display.set_mode((640, 480))
    s = socket.socket()
    s.bind(("127.0.0.1", 1234))
    s.listen(1)
    thread(sender,())
    print "Waiting for connection..."
    client, address = s.accept()
    print "Connection from", address
    thread(listener,(client,))
    MainLoop()
    s.close()
    pygame.quit()