Skip to content

Instantly share code, notes, and snippets.

@PlainSight
Last active December 16, 2023 13:40
Show Gist options
  • Save PlainSight/5f324f9a215e72fb2539454c88ded5bc to your computer and use it in GitHub Desktop.
Save PlainSight/5f324f9a215e72fb2539454c88ded5bc to your computer and use it in GitHub Desktop.
Simple Multiplayer Python Game and Server
import pygame, sys
from pygame.locals import *
import pickle
import select
import socket
WIDTH = 400
HEIGHT = 400
BUFFERSIZE = 2048
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption('Game')
clock = pygame.time.Clock()
sprite1 = pygame.image.load('images/BlueThing/BlueThing_front.png')
sprite2 = pygame.image.load('images/Ladette/Ladette_front.png')
sprite3 = pygame.image.load('images/TrashPanda/TrashPanda_front.png')
sprite4 = pygame.image.load('images/Tubby/Tubby_front.png')
serverAddr = '127.0.0.1'
if len(sys.argv) == 2:
serverAddr = sys.argv[1]
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((serverAddr, 4321))
playerid = 0
sprites = { 0: sprite1, 1: sprite2, 2: sprite3, 3: sprite4 }
class Minion:
def __init__(self, x, y, id):
self.x = x
self.y = y
self.vx = 0
self.vy = 0
self.id = id
def update(self):
self.x += self.vx
self.y += self.vy
if self.x > WIDTH - 50:
self.x = WIDTH - 50
if self.x < 0:
self.x = 0
if self.y > HEIGHT - 50:
self.y = HEIGHT - 50
if self.y < 0:
self.y = 0
if self.id == 0:
self.id = playerid
def render(self):
screen.blit(sprites[self.id % 4], (self.x, self.y))
#game events
#['event type', param1, param2]
#
#event types:
# id update
# ['id update', id]
#
# player locations
# ['player locations', [id, x, y], [id, x, y] ...]
#user commands
# position update
# ['position update', id, x, y]
class GameEvent:
def __init__(self, vx, vy):
self.vx = vx
self.vy = vy
cc = Minion(50, 50, 0)
minions = []
while True:
ins, outs, ex = select.select([s], [], [], 0)
for inm in ins:
gameEvent = pickle.loads(inm.recv(BUFFERSIZE))
if gameEvent[0] == 'id update':
playerid = gameEvent[1]
print(playerid)
if gameEvent[0] == 'player locations':
gameEvent.pop(0)
minions = []
for minion in gameEvent:
if minion[0] != playerid:
minions.append(Minion(minion[1], minion[2], minion[0]))
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
if event.type == KEYDOWN:
if event.key == K_LEFT: cc.vx = -10
if event.key == K_RIGHT: cc.vx = 10
if event.key == K_UP: cc.vy = -10
if event.key == K_DOWN: cc.vy = 10
if event.type == KEYUP:
if event.key == K_LEFT and cc.vx == -10: cc.vx = 0
if event.key == K_RIGHT and cc.vx == 10: cc.vx = 0
if event.key == K_UP and cc.vy == -10: cc.vy = 0
if event.key == K_DOWN and cc.vy == 10: cc.vy = 0
clock.tick(60)
screen.fill((255,255,255))
cc.update()
for m in minions:
m.render()
cc.render()
pygame.display.flip()
ge = ['position update', playerid, cc.x, cc.y]
s.send(pickle.dumps(ge))
s.close()
import socket
import asyncore
import select
import random
import pickle
import time
BUFFERSIZE = 512
outgoing = []
class Minion:
def __init__(self, ownerid):
self.x = 50
self.y = 50
self.ownerid = ownerid
minionmap = {}
def updateWorld(message):
arr = pickle.loads(message)
print(str(arr))
playerid = arr[1]
x = arr[2]
y = arr[3]
if playerid == 0: return
minionmap[playerid].x = x
minionmap[playerid].y = y
remove = []
for i in outgoing:
update = ['player locations']
for key, value in minionmap.items():
update.append([value.ownerid, value.x, value.y])
try:
i.send(pickle.dumps(update))
except Exception:
remove.append(i)
continue
print ('sent update data')
for r in remove:
outgoing.remove(r)
class MainServer(asyncore.dispatcher):
def __init__(self, port):
asyncore.dispatcher.__init__(self)
self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
self.bind(('', port))
self.listen(10)
def handle_accept(self):
conn, addr = self.accept()
print ('Connection address:' + addr[0] + " " + str(addr[1]))
outgoing.append(conn)
playerid = random.randint(1000, 1000000)
playerminion = Minion(playerid)
minionmap[playerid] = playerminion
conn.send(pickle.dumps(['id update', playerid]))
SecondaryServer(conn)
class SecondaryServer(asyncore.dispatcher_with_send):
def handle_read(self):
recievedData = self.recv(BUFFERSIZE)
if recievedData:
updateWorld(recievedData)
else: self.close()
MainServer(4321)
asyncore.loop()
@zywek123
Copy link

zywek123 commented Apr 8, 2020

This server can not send more than 1 event, only position update works. I would send a reply to ping received from client, and it doesn't work.

if arr[0] == 'ping':
and so on, then I'm sending a reply using i.send(pickle.dumps(['pong', playerid])) and it does not work. Client does not receive it.

@superritschy
Copy link

superritschy commented Apr 18, 2020

I had the same problem as zywek123.

The reason is that the server writes to the socket a second time before the client reads from the socket the first message. This means that the client reads multiple pickled objects at once and pickle.loads just returns the first object.
Fortunately concatenated pickled objects can be easily separated:
https://stackoverflow.com/a/61293678/7658204

Copy link

ghost commented Jan 27, 2021

When I run the app in the mac terminal on python 3.9.1, I get the error:

s.connect((serverAddr, 4321))
ConnectionRefusedError: [Errno 61] Connection refused

Is there a solution?

@rain-1107
Copy link

When I run the app in the mac terminal on python 3.9.1, I get the error:

s.connect((serverAddr, 4321))
ConnectionRefusedError: [Errno 61] Connection refused

Is there a solution?

You need to change the address to your local ip or your hoes machine name

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment