Skip to content

Instantly share code, notes, and snippets.

@EncodeTheCode
Created June 29, 2026 16:10
Show Gist options
  • Select an option

  • Save EncodeTheCode/7e581832ea01387f12c3ae0a696f17b0 to your computer and use it in GitHub Desktop.

Select an option

Save EncodeTheCode/7e581832ea01387f12c3ae0a696f17b0 to your computer and use it in GitHub Desktop.
import asyncio
import websockets
import json
import time
PORT = 4445
TICK_RATE = 20
TIMEOUT = 5.0
# =========================
# PLAYER CLASS (FULL)
# =========================
class Player:
def __init__(self, ws):
self.ws = ws
# identity
self.user_id = None
self.authenticated = False
# position (current)
self.x = self.y = self.z = 0.0
# position (previous for interpolation)
self.px = self.py = self.pz = 0.0
# gameplay
self.health = 100
self.armor = 0
self.weapon_id = 0
self.primary_ammo = 30
self.secondary_ammo = 90
self.walking = False
self.running = False
# networking
self.last_seen = time.time()
self.ping = 0.0
# =========================
# AUTH
# =========================
async def auth(self, msg):
self.user_id = msg.get("user_id")
token = msg.get("token")
if self.user_id and token and len(token) > 5:
self.authenticated = True
await self.ws.send(json.dumps({
"type": "auth_ok",
"user_id": self.user_id
}))
# =========================
# APPLY MOVEMENT (with interp support)
# =========================
def move(self, msg):
# store previous BEFORE moving (IMPORTANT for interpolation)
self.px, self.py, self.pz = self.x, self.y, self.z
self.x += float(msg.get("dx", 0))
self.y += float(msg.get("dy", 0))
self.z += float(msg.get("dz", 0))
self.walking = bool(msg.get("walking", False))
self.running = bool(msg.get("running", False))
# =========================
# COMBAT
# =========================
def combat(self, msg):
self.weapon_id = msg.get("weapon_id", self.weapon_id)
self.primary_ammo = msg.get("primary_ammo", self.primary_ammo)
self.secondary_ammo = msg.get("secondary_ammo", self.secondary_ammo)
# =========================
# DAMAGE
# =========================
def damage(self, msg):
dmg = int(msg.get("amount", 0))
absorbed = min(self.armor, dmg)
self.armor -= absorbed
dmg -= absorbed
self.health = max(0, self.health - dmg)
# =========================
# PING
# =========================
def ping_update(self, msg):
client_time = float(msg.get("t", time.time()))
self.ping = time.time() - client_time
# =========================
# TOUCH
# =========================
def touch(self):
self.last_seen = time.time()
# =========================
# SERIALIZE (SEND TO CLIENT)
# includes interpolation data
# =========================
def serialize(self):
return {
"user_id": self.user_id,
"x": self.x,
"y": self.y,
"z": self.z,
"px": self.px,
"py": self.py,
"pz": self.pz,
"health": self.health,
"armor": self.armor,
"weapon_id": self.weapon_id,
"primary_ammo": self.primary_ammo,
"secondary_ammo": self.secondary_ammo,
"walking": self.walking,
"running": self.running,
"ping": round(self.ping, 3)
}
# =========================
# GAME SERVER
# =========================
class GameServer:
def __init__(self):
self.players = {}
def get_player(self, ws):
if ws not in self.players:
self.players[ws] = Player(ws)
return self.players[ws]
# =========================
# HANDLE MESSAGE
# =========================
async def handle(self, ws, msg):
player = self.get_player(ws)
player.touch()
t = msg.get("type")
# AUTH FIRST
if not player.authenticated:
if t == "auth":
await player.auth(msg)
return
if t == "update":
player.move(msg)
elif t == "combat":
player.combat(msg)
elif t == "damage":
player.damage(msg)
elif t == "ping":
player.ping_update(msg)
# =========================
# CLEANUP
# =========================
def cleanup(self):
now = time.time()
dead = []
for ws, p in self.players.items():
if now - p.last_seen > TIMEOUT:
dead.append(ws)
for ws in dead:
del self.players[ws]
# =========================
# WORLD SNAPSHOT
# =========================
def world(self):
return {
"type": "world",
"players": {
str(id(p.ws)): p.serialize()
for p in self.players.values()
}
}
server = GameServer()
# =========================
# WS HANDLER
# =========================
async def handler(ws):
try:
async for message in ws:
msg = json.loads(message)
await server.handle(ws, msg)
finally:
if ws in server.players:
del server.players[ws]
# =========================
# BROADCAST LOOP
# =========================
async def broadcast():
interval = 1 / TICK_RATE
while True:
server.cleanup()
packet = json.dumps(server.world())
dead = []
for p in list(server.players.values()):
try:
await p.ws.send(packet)
except:
dead.append(p.ws)
for ws in dead:
server.players.pop(ws, None)
await asyncio.sleep(interval)
async def main():
async with websockets.serve(handler, "0.0.0.0", PORT):
print(f"Server running ws://localhost:{PORT}")
await broadcast()
asyncio.run(main())
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment