I have programmed a simple clone of Slither.io in python using pygame and sockets and I have three problems:
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.
Your third problem (truncated pickle data) is because you are using TCP, and you are unpickling whatever recv
returns. You might be thinking that whenever you send
something, and the receiver calls recv
, returns the exact same thing, but actually it doesn't. TCP splits your data up into packets, so the receiver might not receive all the data at the same time.
For example, if you send "abcdefgh" and then separately send "ijkl", it's allowed for the first receive to return "abcd" and the second to return "efghijkl". Or the first one could return "ab" and the second one could return "cde" and the third one could return "fghijkl", or so on.
You have to design a way for the receiver to know when to stop receiving. For example, if you sent "8abcdefgh" and then "4ijkl", the receiver could get "8abcdefgh4ij", and then it knows "8abcdefgh" is one "send" (because it starts with 8 and then 8 more bytes) and it knows the "4ij" is the beginning of the next "send" but it's not the whole thing (because it starts with 4 but there aren't 4 more bytes).
Another way is to send a special character like a newline (enter key) after each message. This probably doesn't work with pickles because pickles can have newlines in them. But you could choose another byte that pickles don't have, like 0xFF. Then the receiver knows to keep on receiving until it sees the byte 0xFF.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.