I'm attempting to build a client server game in Python for a Space Invaders for 2 players.
This might be bad practice, but I've used dictionaries to store the positions of the enemies and then use the server to store, update, and send positions to each player.
I send the enemy positions with JSON. I decided against Pickle as I read that it isn't too secure.
Is there a way which would let me somehow get all data without it being cutoff? As I don't think I can stop sending position in whole as the clients get out of sync? As with my server, I end up overloading the client with data that eventually the read buffer gets full and the processing of the data through the server makes it crash.
I use 2 threads on the client side (1 for send/recv each), 3 on the server (1 for each client, 1 to broadcast game updates).
server.py:
import socket
import time
from threading import Thread,Event
import json
import pygame
import math
server_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
server_socket.bind((socket.gethostbyname(socket.gethostname()),7011))
server_socket.listen(2)
print("SERVER IS RUNNING")
connections = []
Threads = []
e = None
DIRECTION = 1
GAMESCREEN_CONSTRAINTS = [422,870]
START_POSITIONS = {"0": "434.0:226.0", "1": "464.0:226.0", "2": "494.0:226.0", "3": "524.0:226.0", "4": "554.0:226.0", "5": "584.0:226.0", "6": "614.0:226.0", "7": "644.0:226.0", "8": "674.0:226.0", "9": "704.0:226.0", "10": "434.0:256.0", "11": "464.0:256.0", "12": "494.0:256.0", "13":
"524.0:256.0", "14": "554.0:256.0", "15": "584.0:256.0", "16": "614.0:256.0", "17": "644.0:256.0", "18": "674.0:256.0", "19": "704.0:256.0", "20": "434.0:286.0",
"21": "464.0:286.0", "22": "494.0:286.0", "23": "524.0:286.0", "24": "554.0:286.0", "25": "584.0:286.0", "26": "614.0:286.0", "27": "644.0:286.0", "28": "674.0:286.0", "29": "704.0:286.0", "30": "434.0:316.0", "31":
"464.0:316.0", "32": "494.0:316.0", "33": "524.0:316.0", "34": "554.0:316.0", "35": "584.0:316.0", "36": "614.0:316.0", "37": "644.0:316.0", "38": "674.0:316.0",
"39": "704.0:316.0", "40": "434.0:346.0", "41": "464.0:346.0", "42": "494.0:346.0", "43": "524.0:346.0", "44": "554.0:346.0", "45": "584.0:346.0", "46": "614.0:346.0", "47": "644.0:346.0", "48": "674.0:346.0", "49":
"704.0:346.0", "50": "434.0:376.0", "51": "464.0:376.0", "52": "494.0:376.0", "53": "524.0:376.0", "54": "554.0:376.0", "55": "584.0:376.0", "56": "614.0:376.0",
"57": "644.0:376.0", "58": "674.0:376.0", "59": "704.0:376.0", "60": "434.0:406.0", "61": "464.0:406.0", "62": "494.0:406.0", "63": "524.0:406.0", "64": "554.0:406.0", "65": "584.0:406.0", "66": "614.0:406.0", "67":
"644.0:406.0", "68": "674.0:406.0", "69": "704.0:406.0"}
Lasers = {}
LASER_INDEX = 0
PLAYER_POSITIONS = [[646.0,648.0],[646.0,648.0]]
def acceptClients():
global e
while len(connections) < 2:
conn,addr = server_socket.accept()
connections.append(conn)
print("Connected to a Client on",addr)
e = Event()
Threads.append(Thread(target = runClient,args=(len(connections)-1,e)))
Threads.append(Thread(target=alienThread,args=(e,)))
def runClient(connectionNo,e):
connections[connectionNo].sendall(("START:" + str(connectionNo) + ",").encode())
while not e.is_set():
try:
data = connections[connectionNo].recv(2048).decode().split("\n")[0]
except ConnectionResetError:
print("A Client disconnected")
try:
connections.pop(connectionNo)
except IndexError:
connections.pop(connectionNo - 1)
print("Closing thread.")
break
else:
if "SHOOT" in data:
shootClient(connectionNo)
elif "p:" in data:
PLAYER_POSITIONS[connectionNo][0] = data.split("p:")[1]
relay(connectionNo,data)
# if not data:
# print("A Client disconnected")
# try:
# connections.pop(connectionNo)
# except IndexError:
# connections.pop(connectionNo - 1)
# print("Closing thread.")
# break
def shootClient(connectionNo):
global LASER_INDEX,Lasers
#Determine Spawnpoint For Laser Using Player Positions angle = 0
center = pygame.Vector2()
center.x = (PLAYER_POSITIONS[0])[0]
center.y = (PLAYER_POSITIONS[0])[1]
radius = 25/2
spawnpoint = [center.x + (math.cos(((0*-1) -90)/180 * math.pi) * radius),center.y + (math.sin(((0*-1) -90)/180 * math.pi) * radius)]
Lasers[LASER_INDEX] = [spawnpoint[0],spawnpoint[1]]
LASER_INDEX += 1
online_lasers = "k:" + jsonEncode(Lasers) + "\n"
broadcast(online_lasers)
def broadcast(string_data):
for connection in connections:
connection.sendall(string_data.encode())
def jsonEncode(raw_data):
json_string = json.dumps(raw_data)
return json_string
def moveDown():
for key in START_POSITIONS:
positionx,positiony = START_POSITIONS[key].split(":")
positiony = float(positiony) + 0.5
START_POSITIONS.update({key:(str(positionx) + ":" + str(positiony))})
def checkCollisions():
global DIRECTION
for key in START_POSITIONS:
if float(START_POSITIONS[key].split(":")[0]) - 10 <= GAMESCREEN_CONSTRAINTS[0] or float(START_POSITIONS[key].split(":")[0]) + 10 >= GAMESCREEN_CONSTRAINTS[1]:
DIRECTION *= - 1
moveDown()
def updateLasers():
global Lasers
for key in Lasers:
x = (Lasers[key])[0]
y = (Lasers[key])[1]
y += 3
Lasers[key] = [x,y]
string = "l:" + jsonEncode(Lasers) + "\n"
# broadcast(string)
def alienThread(e):
global DIRECTION
while not e.is_set():
try:
broadcast("a:" + jsonEncode(START_POSITIONS) + "\n")
except ConnectionResetError:
print("A-Thread ending.")
break
for key in START_POSITIONS:
positionx,positiony = START_POSITIONS[key].split(":")
positionx = float(positionx) + DIRECTION
START_POSITIONS.update({key:(str(positionx) + ":" + str(positiony))})
checkCollisions()
updateLasers()
time.sleep(1/(3*120))
def relay(connectionNo,data):
# print("Client sent: " + data)
if connectionNo - 1 == 0:
connections[0].sendall((str(data) + "\n").encode())
elif connectionNo == 0:
connections[1].sendall((str(data) + "\n").encode())
def startThreads():
for thread in Threads:
thread.start()
def closeThreads():
for thread in Threads:
thread.join()
# def checkThreads():
# global e
# if len(Threads) < 2:
# print("A Client has disconnected.")
# e.set()
def main():
acceptClients()
startThreads()
# checkThreads()
closeThreads()
main()
and the main.py or the client side threads.
def send_online_input(self,e,sentflag):
while not e.is_set():
try:
keys = pygame.key.get_pressed()
if keys[self.player.sprite.keys[0]]:
self.player.sprite.position.x += self.player.sprite.speed
self.client_socket.sendall(("p:" + str(self.player.sprite.position.x) + "\n").encode())
elif keys[self.player.sprite.keys[1]]:
self.player.sprite.position.x -= self.player.sprite.speed
self.client_socket.sendall(("p:" + str(self.player.sprite.position.x) + "\n").encode())
# elif keys[player.sprite.keys[2]]:
# pass
# # player.sprite.turnright()
# # client_socket.sendall((str(player.sprite.keys[2]) + "\n").encode())
# elif keys[player.sprite.keys[3]]:
# pass
# player.sprite.turnleft()
# client_socket.sendall((str(player.sprite.keys[3]) + "\n").encode())
elif keys[self.player.sprite.keys[4]] and self.player.sprite.ready:
self.client_socket.sendall(("SHOOT\n").encode())
sentflag.set()
# client_socket.sendall((str(player.sprite.keys[4]) + "\n").encode())
#player.sprite.shoot()
self.player.sprite.ready = False
self.player.sprite.lasertime = pygame.time.get_ticks()
time.sleep(1/(2*60))
except Exception:
pass
def get_online_input(self,e,sentflag):
mod_aliens = self.aliens.sprites()
while not e.is_set():
data = self.client_socket.recv(4096).decode()
messages = data.split("\n")
for message in messages:
if "a:" in message:
packet = message.split("a:")[1]
packet = json.loads(packet)
for key in packet:
positions = packet[key].split(":")
positionx,positiony = positions[0],positions[1]
positionx = float(positionx)
positiony = float(positiony)
mod_aliens[int(key)].pos.x,mod_aliens[int(key)].pos.y = positionx,positiony
# for key in decodeddata:
# positionx = float(decodeddata[key].split(":")[0])
# positionx = round(positionx)
# aliens[key].pos.x = positionx
elif "k:" in message:
laser_dict = message.split("k:")[1]
laser_dict = json.loads(laser_dict)
for key in laser_dict:
x,y = laser_dict[key][0],laser_dict[key][1]
spawnpoint = (float(x),float(y))
if sentflag.is_set():
self.player.sprite.lasers.add(Laser(0,3,spawnpoint))
sentflag.clear()
else:
self.player2.sprite.lasers.add(Laser(0,3,spawnpoint))
elif len(message) > 0:
# print("SERVER SENT: " + message)
try:
self.do_input(message)
I've tried making the thread sending enemy updates on the server run slower, but it isn't too great of a solution, as the game will run then slower.
I've tried thinking of not overloading them by sending just inputs across the server, then sending a big update every so often, but I feel eventually the read buffer (4096 - already quite high in my opinion) will get full and I won't get correct data to process.