I have programmed a simple clone of Slither.io in python using pygame and sockets and I have three problems:
- When I play the game alone on my laptop the game is choppy. Every ten seconds my game get stuck for a while (one milisecond) and then continue. It's not a big problem but it's annoying.
- When I play on two computers in my local network, I see the other player (the other snake) is also choppy.
- The strangest problem is when I run my server on my main laptop and then run the game on my second laptop the game starts and after few seconds crash. Debugger on client says that pickle data was truncated while receiving data from the server. But when I run the server program on my second laptop and the game on my main laptop everything is OK. Why?
I tried:
Problem 1. change FPS on the client and time.sleep on the server
Problem 2. change time.sleep on the server
Problem 3. change the input value of recv() method
Server code:
import socket
import threading
import pickle
import random
import time
import math
ip = socket.gethostbyname(socket.gethostname())
port = 5555
address = (ip, port)
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
snakes = []
colors = [(80, 0, 0), (0, 80, 0), (0, 0, 140), (80, 80, 0), (80, 0, 80), (0, 80, 80)]
food = []
for i in range(0, 300, 1):
food.append([random.randint(10, 3290), random.randint(10, 2090), (0, 0, 100), 10, 1])
number_of_players = 0
def start(server, address):
server.bind(address)
def new_clients(server):
global number_of_players
server.listen()
while True:
conn, addr = server.accept()
thread = threading.Thread(target=client, args=(conn, addr, number_of_players))
thread.start()
number_of_players += 1
print(f"Active connections: {threading.activeCount() - 1}")
def client(conn, addr, player):
print(f"New connection: {addr}\n")
global snakes
global colors
global food
try:
snakes.append([])
name = pickle.loads(conn.recv(1024))
while True:
verity = True
x = random.randint(100, 3200)
y = random.randint(100, 2000)
for i in range(0, len(snakes), 1):
if snakes[i] != []:
if math.sqrt((snakes[i][0][0] - x)**2 + (snakes[i][0][1] - y)**2) > snakes[i][0][4]*2:
for a in range(0, len(snakes[i][1])):
if math.sqrt((snakes[i][1][a][0] - x)**2 + (snakes[i][1][a][1] - y)**2) < snakes[i][0][4]*2:
verity = False
if verity == False:
break
else:
verity = False
if verity == False:
break
if verity == True:
break
snakes[player] = [[x, y, random.choice(colors), 4, 30, name, 0, 3], []]
conn.send(pickle.dumps(snakes[player]))
except (ConnectionAbortedError, ConnectionResetError, EOFError):
print("Connection lost")
print(f"Active connections: {threading.activeCount() - 2}")
conn.close()
return
while True:
try:
snakes_to_send = []
for i in range(0, len(snakes), 1):
if (i != player) and (len(snakes[i]) != 0):
snakes_to_send.append(snakes[i])
conn.send(pickle.dumps([snakes_to_send, food]))
received = pickle.loads(conn.recv(8192*4))
snakes[player] = received[0]
eaten = received[1]
for i in range(0, len(eaten), 1):
try:
food.remove(eaten[i])
if eaten[i][4] != 5:
food.append([random.randint(10, 3290), random.randint(10, 2090), (0, 0, 100), 10, 1])
except ValueError:
continue
dead_snake = received[2]
if dead_snake != []:
for i in range(0, len(dead_snake), 1):
food.append(dead_snake[i])
time.sleep(1/1000)
except (ConnectionAbortedError, ConnectionResetError, EOFError):
snakes[player] = []
print("Connection lost")
print(f"Active connections: {threading.activeCount() - 2}")
conn.close()
break
print(f"Server is starting on IP: {ip} and PORT: {port}")
start(server, address)
new_clients(server)
The game:
#import
import pygame
import sys
import math
import random
import pygame_textinput
from client import Client
#pygame initialize
pygame.init()
win = pygame.display.set_mode((1100, 700))
pygame.display.set_caption("Slither.io")
#connect to the server
ip = "10.0.0.3"
port = 5555
c = Client(ip, port)
c.server()
#fonts
font = pygame.font.SysFont("comicsansms", 20)
big_font = pygame.font.SysFont("comicsansms", 35)
#time
clock = pygame.time.Clock()
class Snake:
def __init__(self, x, y, color, length, diameter, name, score, step):
self.x = x
self.y = y
self.color = color
self.length = length
self.diameter = diameter
self.name = name
self.score = score
self.step = step
self.pos_list = []
#move the snake
def move(self):
self.mousex, self.mousey = pygame.mouse.get_pos()
self.difx = self.mousex - 550
self.dify = self.mousey - 350
try:
self.xstep = math.sqrt((self.step**2)/((self.dify/self.difx)**2 + 1))
self.ystep = (self.dify/self.difx)*self.xstep
except ZeroDivisionError:
self.xstep = 0
self.ystep = self.step
if math.sqrt(self.difx**2 + self.dify**2) > self.diameter/3:
if(self.difx > 0):
self.x += self.xstep
self.y += self.ystep
elif(self.difx < 0):
self.x -= self.xstep
self.y -= self.ystep
else:
if self.dify > 0:
self.y += self.ystep
else:
self.y -= self.ystep
self.pos_list.append([self.x, self.y])
if len(self.pos_list) == 100:
del self.pos_list[0]
#check if the snake isn't out of the area or not crashed
def collision(self):
global run
if (self.x + self.diameter/3 >= 3299) or (self.x - self.diameter/3 <= 1) or (self.y + self.diameter/3 >= 2099) or (self.y - self.diameter/3 <= 1):
run = False
for i in range(0, len(other_snakes), 1):
if math.sqrt((other_snakes[i].x - snake.x)**2 + (other_snakes[i].y - snake.y)**2) < other_snakes[i].diameter*(2/3):
run = False
for i in range(0, len(other_segments), 1):
if math.sqrt((other_segments[i].x - snake.x)**2 + (other_segments[i].y - snake.y)**2) < other_segments[i].diameter*(2/3):
run = False
#draw all snakes
def draw(self):
pygame.draw.rect(win, self.color, [self.x - snake.x + 550 - self.diameter/2, self.y - snake.y + 350 - self.diameter/2, self.diameter, self.diameter], border_radius=int(self.diameter/2))
class Segment(Snake):
def __init__(self, x, y, diameter, step, circle):
self.x = x
self.y = y
self.color = (80, 80, 80)
self.diameter = diameter
self.step = step
self.circle = circle
self.pos_list = []
def move(self):
self.diameter = snake.diameter
try:
self.x = self.circle.pos_list[-int(self.diameter/self.step)][0]
self.y = self.circle.pos_list[-int(self.diameter/self.step)][1]
except IndexError:
self.x = self.circle.pos_list[0][0]
self.y = self.circle.pos_list[0][1]
self.pos_list.append([self.x, self.y])
if len(self.pos_list) == 100:
del self.pos_list[0]
class Food(Snake):
def __init__(self, x, y, color, diameter, points):
self.x = x
self.y = y
self.color = color
self.diameter = diameter
self.points = points
other_snakes = []
segments = []
other_segments = []
food = []
eaten = []
dead_snake = []
#get other snakes
def return_snakes():
global other_snakes
global other_segments
global food
other_snakes = []
other_segments = []
food = []
message = c.receive_message()
received = message[0]
for i in range(0, len(received), 1):
s = Snake(received[i][0][0], received[i][0][1], received[i][0][2], received[i][0][3], received[i][0][4], received[i][0][5], received[i][0][6], snake.step)
other_snakes.append(s)
for a in range(0, len(received[i][1]), 1):
sg = Segment(received[i][1][a][0], received[i][1][a][1], s.diameter, snake.step, 0)
other_segments.append(sg)
rec_fd = message[1]
for i in range(0, len(rec_fd), 1):
f = Food(rec_fd[i][0], rec_fd[i][1], rec_fd[i][2], rec_fd[i][3], rec_fd[i][4])
food.append(f)
#send the snake to server
def send_snake():
global snake
global segments
global food
to_send = [[[snake.x, snake.y, snake.color, snake.length, snake.diameter, snake.name, snake.score], []]]
for i in range(0, len(segments), 1):
to_send[0][1].append([segments[i].x, segments[i].y])
to_send.append(eaten)
to_send.append(dead_snake)
c.send_message(to_send)
#function for ordering snakes by score
def order(o):
return o["score"]
#draw screen
def screen():
for i in range(0, 3301, 100):
pygame.draw.line(win, (60, 60, 60), [i - snake.x + 550, 0 - snake.y + 350], [i - snake.x + 550, 2100 - snake.y + 350], 1)
for i in range(0, 2101, 100):
pygame.draw.line(win, (60, 60, 60), [0 - snake.x + 550, i - snake.y + 350], [3300 - snake.x + 550, i - snake.y + 350], 1)
#enter your nickname
textinput = pygame_textinput.TextInput(font_family="comicsansms", max_string_length=12)
while True:
win.fill((100, 100, 100))
events = pygame.event.get()
for event in events:
keys = pygame.key.get_pressed()
if (event.type == pygame.QUIT) or keys[pygame.K_ESCAPE] or keys[pygame.K_F4] and pygame.key.get_mods() & pygame.KMOD_ALT:
sys.exit()
if keys[pygame.K_RETURN]:
break
name = big_font.render("Nickname:", True, (0, 0, 0))
center = name.get_rect(center=(400, 350))
win.blit(name, center)
textinput.update(events)
win.blit(textinput.get_surface(), (500, 325))
pygame.display.update()
c.send_message(textinput.get_text())
received = c.receive_message()
snake = Snake(received[0][0], received[0][1], received[0][2], received[0][3], received[0][4], received[0][5], received[0][6], received[0][7])
segment = Segment(snake.x, snake.y, snake.diameter, snake.step, snake)
segments.append(segment)
for i in range(0, snake.length - 2, 1):
segment = Segment(snake.x, snake.y, snake.diameter, snake.step, segment)
segments.append(segment)
while True:
run = True
while run:
eaten = []
#quit game
for event in pygame.event.get():
keys = pygame.key.get_pressed()
if (event.type == pygame.QUIT) or keys[pygame.K_ESCAPE] or keys[pygame.K_F4] and pygame.key.get_mods() & pygame.KMOD_ALT:
sys.exit()
return_snakes()
snakes = [{"name": snake.name, "score": snake.score}]
for i in range(0, len(other_snakes), 1):
snakes.append({"name": other_snakes[i].name, "score": other_snakes[i].score})
win.fill((100, 100, 100))
screen()
#draw all food
for i in range(0, len(food), 1):
food[i].draw()
#draw the other snakes and describe
for i in range(0, len(other_segments), 1):
other_segments[i].draw()
for i in range(0, len(other_snakes), 1):
other_snakes[i].draw()
name = font.render(other_snakes[i].name, True, (0, 0, 0))
center = name.get_rect(center=(other_snakes[i].x - snake.x + 550, other_snakes[i].y - snake.y + 350))
win.blit(name, center)
#move, draw snake and check if the snake is alive
snake.move()
for i in range(0, len(segments), 1):
segments[i].move()
for i in range(0, len(segments), 1):
segments[i].draw()
snake.draw()
snake.collision()
#check if the snake ate food and add score
for i in range(len(food) - 1, -1, -1):
if math.sqrt((food[i].x - snake.x)**2 + (food[i].y - snake.y)**2) < (snake.diameter/2 + food[i].diameter/2):
snake.score += food[i].points
snake.length = int(snake.score/10) + 4
if snake.length - 1 > len(segments):
segment = Segment(snake.x, snake.y, snake.diameter, snake.step, segment)
segments.append(segment)
snake.diameter = int(snake.score/50)*0.5 + 30
eaten.append([food[i].x, food[i].y, food[i].color, food[i].diameter, food[i].points])
#describe the snake
name = font.render(snake.name, True, (0, 0, 0))
center = name.get_rect(center=(550, 350))
win.blit(name, center)
send_snake()
#sort the snakes by score
snakes.sort(key=order, reverse=True)
#create the table
for i in range(0, len(snakes), 1):
table = font.render(str(snakes[i]["name"]), True, (0, 0, 0))
win.blit(table, (900, 50 + i*30))
for i in range(0, len(snakes), 1):
table = font.render(str(snakes[i]["score"]), True, (0, 0, 0))
win.blit(table, (1050, 50 + i*30))
for i in range(0, len(snakes), 1):
table = font.render(str(i + 1) + ".", True, (0, 0, 0))
win.blit(table, (870, 50 + i*30))
pygame.display.update()
clock.tick(140)
return_snakes()
#create food from head of dead snake
ds = [snake.x, snake.y, snake.color, 15, 5]
dead_snake.append(ds)
#create food from segments of dead snake
for i in range(0, len(segments), 1):
ds = [segments[i].x, segments[i].y, snake.color, 15, 5]
dead_snake.append(ds)
send_snake()
dead_snake = []
clock.tick(140)
c.client.close()
while True:
#quit game
for event in pygame.event.get():
keys = pygame.key.get_pressed()
if (event.type == pygame.QUIT) or keys[pygame.K_ESCAPE] or keys[pygame.K_F4] and pygame.key.get_mods() & pygame.KMOD_ALT:
sys.exit()
if keys[pygame.K_SPACE]:
c = Client(ip, port)
c.server()
c.send_message(snake.name)
received = c.receive_message()
snake = Snake(received[0][0], received[0][1], received[0][2], received[0][3], received[0][4], received[0][5], received[0][6], received[0][7])
segments = []
segment = Segment(snake.x, snake.y, snake.diameter, snake.step, snake)
segments.append(segment)
for i in range(0, snake.length - 2, 1):
segment = Segment(snake.x, snake.y, snake.diameter, snake.step, segment)
segments.append(segment)
break
win.fill((100, 100, 100))
screen()
#press space bar to continue
cont = big_font.render("Press space bar to continue", True, (0, 0, 0))
center = cont.get_rect(center=(550, 350))
win.blit(cont, center)
pygame.display.update()
clock.tick(140)
Client class file:
import socket
import pickle
ip = "10.0.0.3"
port = 5555
class Client:
def __init__(self, ip, port):
self.ip = ip
self.port = port
self.address = (self.ip, self.port)
self.client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
def server(self):
try:
self.client.connect(self.address)
print(f"Successfully connected to {self.address}")
except:
pass
def send_message(self, message):
self.client.sendall(pickle.dumps(message))
def receive_message(self):
return pickle.loads(self.client.recv(8192*4))
And this is pygame_textinput library I'm using here.