import socketio import eventlet import sqlite3 import re from datetime import date import os # Create a Socket.IO server (CORS arg required on server, not locally) sio = socketio.Server() #sio = socketio.Server(cors_allowed_origins='URL or *') RPSLS_PATH = './' #edit if launched from elsewhere DB_PATH = RPSLS_PATH + 'db/rpsls.sqlite' searching = {} #someone seeks a game? (uid + sid) connected = {} #map uid --> sid (seek stage) @sio.event def disconnect(sid): """ Triggered at page reload or tab close """ global connected, searching try: key_idx = list(connected.values()).index(sid) del connected[list(connected.keys())[key_idx]] except ValueError: # If the user didn't seek, no key to find pass if searching and searching["sid"] == sid: searching = {} @sio.event def login(sid, data): """ When user sends name from /login page """ if not re.match(r"^[a-zA-Z]{3,}$", data): sio.emit("login", {"err": "Name: letters only"}, room=sid) return con = sqlite3.connect(DB_PATH) cur = con.cursor() uid = 0 try: # Always try to insert (new) Users row cur.execute("insert into Users (name) values (?)", (data,)) uid = cur.lastrowid except sqlite3.IntegrityError as err: # If fails: user already exists, find its ID if str(err) == "UNIQUE constraint failed: Users.name": cur.execute("select id from Users where name = ?", (data,)) uid = cur.fetchone()[0] else: raise con.commit() con.close() sio.emit("login", {"name": data, "uid": uid}, room=sid) @sio.event def seek(sid, data): """ When user click on 'Play' button """ global connected, searching connected[data["uid"]] = sid if not searching: searching = {"uid": data["uid"], "sid": sid, "name": data["name"]} else: # Active seek pending: create game opponent = searching searching = {} con = sqlite3.connect(DB_PATH) cur = con.cursor() today = (date.today(),) cur.execute("insert into Games (created) values (?)", today) gid = cur.lastrowid # To room == sid, opponent is me. To my room, it's him/her sio.emit("play", {"gid":gid, "oppid":opponent["uid"], "oppname":opponent["name"]}, room=sid) sio.emit("play", {"gid":gid, "oppid":data["uid"], "oppname":data["name"]}, room=opponent["sid"]) id_list = [(data["uid"],gid), (opponent["uid"],gid)] cur.executemany("insert into Players (uid,gid) values (?,?)", id_list) con.commit() con.close() @sio.event def move(sid, data): """ New move to DB + transmit to opponent """ sio.emit("move", data, room=connected[data["oppid"]]) con = sqlite3.connect(DB_PATH) cur = con.cursor() cur.execute("insert into Moves (uid,gid,choice,mnum) values (?,?,?,?)", (data["uid"],data["gid"],data["choice"],data["mnum"])) con.commit() con.close() static_files = { '/': RPSLS_PATH + 'index.html', '/rpsls.js': RPSLS_PATH + 'rpsls.js', '/favicon.ico': RPSLS_PATH + 'favicon.ico', '/assets': RPSLS_PATH + 'assets' } PORT = os.getenv('RPSLS_PORT') if PORT is None: PORT = "8000" PORT = int(PORT) # Wrap with a WSGI application app = socketio.WSGIApp(sio, static_files=static_files) eventlet.wsgi.server(eventlet.listen(('127.0.0.1', PORT)), app)