taiko-web/server.py

400 lines
13 KiB
Python
Raw Normal View History

2020-12-22 09:17:38 +01:00
#!/usr/bin/env python3
2018-09-12 19:10:00 +02:00
import asyncio
import websockets
import json
2018-11-01 23:05:18 +01:00
import random
2018-11-03 16:21:21 +01:00
import sys
2018-09-12 19:10:00 +02:00
server_status = {
2018-09-15 16:34:53 +02:00
"waiting": {},
2018-11-01 23:05:18 +01:00
"users": [],
"invites": {}
2018-09-12 19:10:00 +02:00
}
2018-11-01 23:05:18 +01:00
consonants = "bcdfghjklmnpqrstvwxyz"
2018-09-12 19:10:00 +02:00
2020-04-02 00:37:23 +02:00
def msgobj(msg_type, value=None):
2018-09-12 19:10:00 +02:00
if value == None:
2020-04-02 00:37:23 +02:00
return json.dumps({"type": msg_type})
2018-09-12 19:10:00 +02:00
else:
2020-04-02 00:37:23 +02:00
return json.dumps({"type": msg_type, "value": value})
2018-09-12 19:10:00 +02:00
def status_event():
value = []
for id, userDiff in server_status["waiting"].items():
value.append({
"id": id,
"diff": userDiff["diff"]
})
return msgobj("users", value)
2018-11-01 23:05:18 +01:00
def get_invite():
return "".join([random.choice(consonants) for x in range(5)])
2018-09-12 19:10:00 +02:00
async def notify_status():
2018-09-15 16:34:53 +02:00
ready_users = [user for user in server_status["users"] if "ws" in user and user["action"] == "ready"]
2018-09-12 19:10:00 +02:00
if ready_users:
sent_msg = status_event()
await asyncio.wait([user["ws"].send(sent_msg) for user in ready_users])
async def connection(ws, path):
# User connected
user = {
"ws": ws,
2018-11-01 23:05:18 +01:00
"action": "ready",
2020-03-13 03:34:54 +01:00
"session": False,
"name": None,
"don": None
2018-09-12 19:10:00 +02:00
}
2018-09-15 16:34:53 +02:00
server_status["users"].append(user)
2018-09-12 19:10:00 +02:00
try:
# Notify user about other users
await ws.send(status_event())
while True:
try:
message = await asyncio.wait_for(ws.recv(), timeout=10)
2018-09-12 19:10:00 +02:00
except asyncio.TimeoutError:
# Keep user connected
pong_waiter = await ws.ping()
try:
await asyncio.wait_for(pong_waiter, timeout=10)
2018-09-12 19:10:00 +02:00
except asyncio.TimeoutError:
# Disconnect
break
2018-09-18 00:37:59 +02:00
except websockets.exceptions.ConnectionClosed:
# Connection closed
break
2018-09-12 19:10:00 +02:00
else:
# Message received
try:
data = json.loads(message)
except json.decoder.JSONDecodeError:
data = {}
action = user["action"]
2020-04-02 00:37:23 +02:00
msg_type = data["type"] if "type" in data else None
2018-09-12 19:10:00 +02:00
value = data["value"] if "value" in data else None
if action == "ready":
# Not playing or waiting
2020-04-02 00:37:23 +02:00
if msg_type == "join":
2018-11-01 23:05:18 +01:00
if value == None:
continue
2018-09-12 19:10:00 +02:00
waiting = server_status["waiting"]
id = value["id"] if "id" in value else None
diff = value["diff"] if "diff" in value else None
2020-03-13 03:34:54 +01:00
user["name"] = value["name"] if "name" in value else None
user["don"] = value["don"] if "don" in value else None
2018-09-12 19:10:00 +02:00
if not id or not diff:
continue
if id not in waiting:
# Wait for another user
user["action"] = "waiting"
user["gameid"] = id
waiting[id] = {
"user": user,
"diff": diff
}
await ws.send(msgobj("waiting"))
else:
# Join the other user and start game
2020-03-13 03:34:54 +01:00
user["name"] = value["name"] if "name" in value else None
user["don"] = value["don"] if "don" in value else None
2018-09-12 19:10:00 +02:00
user["other_user"] = waiting[id]["user"]
waiting_diff = waiting[id]["diff"]
del waiting[id]
if "ws" in user["other_user"]:
user["action"] = "loading"
user["other_user"]["action"] = "loading"
user["other_user"]["other_user"] = user
user["other_user"]["player"] = 1
user["player"] = 2
2018-09-12 19:10:00 +02:00
await asyncio.wait([
ws.send(msgobj("gameload", {"diff": waiting_diff, "player": 2})),
user["other_user"]["ws"].send(msgobj("gameload", {"diff": diff, "player": 1})),
ws.send(msgobj("name", {
"name": user["other_user"]["name"],
"don": user["other_user"]["don"]
})),
user["other_user"]["ws"].send(msgobj("name", {
"name": user["name"],
"don": user["don"]
}))
2018-09-12 19:10:00 +02:00
])
else:
# Wait for another user
2018-11-01 23:05:18 +01:00
del user["other_user"]
2018-09-12 19:10:00 +02:00
user["action"] = "waiting"
user["gameid"] = id
waiting[id] = {
"user": user,
"diff": diff
}
await ws.send(msgobj("waiting"))
# Update others on waiting players
await notify_status()
2020-04-02 00:37:23 +02:00
elif msg_type == "invite":
2020-03-13 03:34:54 +01:00
if value and "id" in value and value["id"] == None:
2018-11-01 23:05:18 +01:00
# Session invite link requested
invite = get_invite()
server_status["invites"][invite] = user
user["action"] = "invite"
user["session"] = invite
2020-03-13 03:34:54 +01:00
user["name"] = value["name"] if "name" in value else None
user["don"] = value["don"] if "don" in value else None
2018-11-01 23:05:18 +01:00
await ws.send(msgobj("invite", invite))
2020-03-13 03:34:54 +01:00
elif value and "id" in value and value["id"] in server_status["invites"]:
2018-11-01 23:05:18 +01:00
# Join a session with the other user
2020-03-13 03:34:54 +01:00
user["name"] = value["name"] if "name" in value else None
user["don"] = value["don"] if "don" in value else None
2020-03-13 03:34:54 +01:00
user["other_user"] = server_status["invites"][value["id"]]
del server_status["invites"][value["id"]]
2018-11-01 23:05:18 +01:00
if "ws" in user["other_user"]:
user["other_user"]["other_user"] = user
user["action"] = "invite"
2020-03-13 03:34:54 +01:00
user["session"] = value["id"]
user["other_user"]["player"] = 1
user["player"] = 2
2018-11-01 23:05:18 +01:00
await asyncio.wait([
ws.send(msgobj("session", {"player": 2})),
user["other_user"]["ws"].send(msgobj("session", {"player": 1})),
2020-03-13 03:34:54 +01:00
ws.send(msgobj("invite")),
ws.send(msgobj("name", {
"name": user["other_user"]["name"],
"don": user["other_user"]["don"]
})),
user["other_user"]["ws"].send(msgobj("name", {
"name": user["name"],
"don": user["don"]
}))
2018-11-01 23:05:18 +01:00
])
else:
del user["other_user"]
await ws.send(msgobj("gameend"))
else:
# Session code is invalid
await ws.send(msgobj("gameend"))
2018-09-12 19:10:00 +02:00
elif action == "waiting" or action == "loading" or action == "loaded":
# Waiting for another user
2020-04-02 00:37:23 +02:00
if msg_type == "leave":
2018-09-12 19:10:00 +02:00
# Stop waiting
2018-11-13 05:36:15 +01:00
if user["session"]:
if "other_user" in user and "ws" in user["other_user"]:
user["action"] = "songsel"
await asyncio.wait([
ws.send(msgobj("left")),
user["other_user"]["ws"].send(msgobj("users", []))
])
else:
user["action"] = "ready"
user["session"] = False
await asyncio.wait([
ws.send(msgobj("gameend")),
ws.send(status_event())
])
else:
del server_status["waiting"][user["gameid"]]
del user["gameid"]
user["action"] = "ready"
await asyncio.wait([
ws.send(msgobj("left")),
notify_status()
])
2018-09-12 19:10:00 +02:00
if action == "loading":
2020-04-02 00:37:23 +02:00
if msg_type == "gamestart":
2018-09-12 19:10:00 +02:00
user["action"] = "loaded"
if user["other_user"]["action"] == "loaded":
user["action"] = "playing"
user["other_user"]["action"] = "playing"
sent_msg = msgobj("gamestart")
await asyncio.wait([
ws.send(sent_msg),
user["other_user"]["ws"].send(sent_msg)
])
elif action == "playing":
# Playing with another user
if "other_user" in user and "ws" in user["other_user"]:
2020-04-02 00:37:23 +02:00
if msg_type == "note"\
or msg_type == "drumroll"\
or msg_type == "branch"\
or msg_type == "gameresults":
await user["other_user"]["ws"].send(msgobj(msg_type, value))
elif msg_type == "songsel" and user["session"]:
2018-11-01 23:05:18 +01:00
user["action"] = "songsel"
user["other_user"]["action"] = "songsel"
sent_msg1 = msgobj("songsel")
2018-11-01 23:05:18 +01:00
sent_msg2 = msgobj("users", [])
await asyncio.wait([
ws.send(sent_msg1),
ws.send(sent_msg2),
user["other_user"]["ws"].send(sent_msg1),
user["other_user"]["ws"].send(sent_msg2)
])
2020-04-02 00:37:23 +02:00
elif msg_type == "gameend":
2018-11-01 23:05:18 +01:00
# User wants to disconnect
user["action"] = "ready"
user["other_user"]["action"] = "ready"
sent_msg1 = msgobj("gameend")
sent_msg2 = status_event()
await asyncio.wait([
ws.send(sent_msg1),
ws.send(sent_msg2),
user["other_user"]["ws"].send(sent_msg1),
user["other_user"]["ws"].send(sent_msg2)
])
del user["other_user"]["other_user"]
2018-11-01 23:05:18 +01:00
del user["other_user"]
else:
# Other user disconnected
user["action"] = "ready"
user["session"] = False
await asyncio.wait([
ws.send(msgobj("gameend")),
ws.send(status_event())
])
elif action == "invite":
2020-04-02 00:37:23 +02:00
if msg_type == "leave":
2018-11-01 23:05:18 +01:00
# Cancel session invite
if user["session"] in server_status["invites"]:
del server_status["invites"][user["session"]]
user["action"] = "ready"
user["session"] = False
if "other_user" in user and "ws" in user["other_user"]:
user["other_user"]["action"] = "ready"
user["other_user"]["session"] = False
sent_msg = status_event()
await asyncio.wait([
ws.send(msgobj("left")),
ws.send(sent_msg),
user["other_user"]["ws"].send(msgobj("gameend")),
user["other_user"]["ws"].send(sent_msg)
])
else:
await asyncio.wait([
ws.send(msgobj("left")),
ws.send(status_event())
])
2020-04-02 00:37:23 +02:00
elif msg_type == "songsel" and "other_user" in user:
2018-11-01 23:05:18 +01:00
if "ws" in user["other_user"]:
user["action"] = "songsel"
user["other_user"]["action"] = "songsel"
2020-04-02 00:37:23 +02:00
sent_msg = msgobj(msg_type)
2018-11-01 23:05:18 +01:00
await asyncio.wait([
ws.send(sent_msg),
user["other_user"]["ws"].send(sent_msg)
])
else:
user["action"] = "ready"
user["session"] = False
await asyncio.wait([
ws.send(msgobj("gameend")),
ws.send(status_event())
])
elif action == "songsel":
# Session song selection
if "other_user" in user and "ws" in user["other_user"]:
2020-04-02 00:37:23 +02:00
if msg_type == "songsel" or msg_type == "catjump":
2018-11-01 23:05:18 +01:00
# Change song select position
if user["other_user"]["action"] == "songsel" and type(value) is dict:
value["player"] = user["player"]
2020-04-02 00:37:23 +02:00
sent_msg = msgobj(msg_type, value)
2018-11-01 23:05:18 +01:00
await asyncio.wait([
ws.send(sent_msg),
user["other_user"]["ws"].send(sent_msg)
])
2020-04-02 00:37:23 +02:00
elif msg_type == "crowns" or msg_type == "getcrowns":
2020-03-16 20:49:18 +01:00
if user["other_user"]["action"] == "songsel":
2020-04-02 00:37:23 +02:00
sent_msg = msgobj(msg_type, value)
2020-03-16 20:49:18 +01:00
await asyncio.wait([
user["other_user"]["ws"].send(sent_msg)
])
2020-04-02 00:37:23 +02:00
elif msg_type == "join":
2018-11-01 23:05:18 +01:00
# Start game
if value == None:
continue
id = value["id"] if "id" in value else None
diff = value["diff"] if "diff" in value else None
if not id or not diff:
continue
if user["other_user"]["action"] == "waiting":
user["action"] = "loading"
user["other_user"]["action"] = "loading"
await asyncio.wait([
ws.send(msgobj("gameload", {"diff": user["other_user"]["gamediff"]})),
user["other_user"]["ws"].send(msgobj("gameload", {"diff": diff}))
2018-11-01 23:05:18 +01:00
])
else:
user["action"] = "waiting"
user["gamediff"] = diff
await user["other_user"]["ws"].send(msgobj("users", [{
"id": id,
"diff": diff
}]))
2020-04-02 00:37:23 +02:00
elif msg_type == "gameend":
2018-09-12 19:10:00 +02:00
# User wants to disconnect
user["action"] = "ready"
2018-11-01 23:05:18 +01:00
user["session"] = False
2018-09-12 19:10:00 +02:00
user["other_user"]["action"] = "ready"
2018-11-01 23:05:18 +01:00
user["other_user"]["session"] = False
2018-09-12 19:10:00 +02:00
sent_msg1 = msgobj("gameend")
sent_msg2 = status_event()
await asyncio.wait([
ws.send(sent_msg1),
user["other_user"]["ws"].send(sent_msg1)
])
await asyncio.wait([
2018-09-12 19:10:00 +02:00
ws.send(sent_msg2),
user["other_user"]["ws"].send(sent_msg2)
])
del user["other_user"]["other_user"]
2018-09-12 19:10:00 +02:00
del user["other_user"]
else:
# Other user disconnected
user["action"] = "ready"
2018-11-01 23:05:18 +01:00
user["session"] = False
2018-09-12 19:10:00 +02:00
await asyncio.wait([
ws.send(msgobj("gameend")),
ws.send(status_event())
])
finally:
# User disconnected
del user["ws"]
2018-09-15 16:34:53 +02:00
del server_status["users"][server_status["users"].index(user)]
2018-09-12 19:10:00 +02:00
if "other_user" in user and "ws" in user["other_user"]:
user["other_user"]["action"] = "ready"
2018-11-01 23:05:18 +01:00
user["other_user"]["session"] = False
2018-09-12 19:10:00 +02:00
await asyncio.wait([
user["other_user"]["ws"].send(msgobj("gameend")),
user["other_user"]["ws"].send(status_event())
])
del user["other_user"]["other_user"]
2018-09-12 19:10:00 +02:00
if user["action"] == "waiting":
del server_status["waiting"][user["gameid"]]
await notify_status()
2018-11-01 23:05:18 +01:00
elif user["action"] == "invite" and user["session"] in server_status["invites"]:
del server_status["invites"][user["session"]]
2018-09-12 19:10:00 +02:00
2018-11-03 16:21:21 +01:00
port = int(sys.argv[1]) if len(sys.argv) > 1 else 34802
print('Starting server on port %d' % port)
loop = asyncio.get_event_loop()
tasks = asyncio.gather(
2018-11-03 16:21:21 +01:00
websockets.serve(connection, "localhost", port)
2018-09-12 19:10:00 +02:00
)
try:
loop.run_until_complete(tasks)
loop.run_forever()
except KeyboardInterrupt:
print("Stopping server")
def shutdown_exception_handler(loop, context):
if "exception" not in context or not isinstance(context["exception"], asyncio.CancelledError):
loop.default_exception_handler(context)
loop.set_exception_handler(shutdown_exception_handler)
tasks = asyncio.gather(*asyncio.all_tasks(loop=loop), loop=loop, return_exceptions=True)
tasks.add_done_callback(lambda t: loop.stop())
tasks.cancel()
while not tasks.done() and not loop.is_closed():
loop.run_forever()
finally:
if hasattr(loop, "shutdown_asyncgens"):
loop.run_until_complete(loop.shutdown_asyncgens())
loop.close()