2f4acff9b6ef3bf846ec9e0a968735089c450a62
[rpsls-web.git] / server.py
1 import socketio
2 import eventlet
3 import sqlite3
4 from re import match as re_match
5 from datetime import date
6 from os import getenv
7 from sys import path as sys_path
8
9 # Create a Socket.IO server (CORS arg required on server, not locally)
10 MODE = getenv('RPSLS_MODE')
11 allowed_origin = 'https://rpsls.auder.net' if MODE=='production' else '*'
12 sio = socketio.Server(cors_allowed_origins=allowed_origin)
13
14 RPSLS_PATH = sys_path[0]
15 DB_PATH = RPSLS_PATH + '/db/rpsls.sqlite'
16
17 searching = {} #someone seeks a game? (uid + sid)
18 connected = {} #map uid --> sid (seek stage)
19
20 @sio.event
21 def disconnect(sid):
22 """ Triggered at page reload or tab close """
23 global connected, searching
24 try:
25 key_idx = list(connected.values()).index(sid)
26 del connected[list(connected.keys())[key_idx]]
27 except ValueError:
28 # If the user didn't seek, no key to find
29 pass
30 if searching and searching["sid"] == sid:
31 searching = {}
32
33 @sio.event
34 def login(sid, data):
35 """ When user sends name from /login page """
36 if not re_match(r"^[a-zA-Z]{3,}$", data):
37 sio.emit("login", {"err": "Name: letters only"}, room=sid)
38 return
39 con = sqlite3.connect(DB_PATH)
40 cur = con.cursor()
41 uid = 0
42 try:
43 # Always try to insert (new) Users row
44 cur.execute("insert into Users (name) values (?)", (data,))
45 uid = cur.lastrowid
46 except sqlite3.IntegrityError as err:
47 # If fails: user already exists, find its ID
48 if str(err) == "UNIQUE constraint failed: Users.name":
49 cur.execute("select id from Users where name = ?", (data,))
50 uid = cur.fetchone()[0]
51 else:
52 raise
53 con.commit()
54 con.close()
55 sio.emit("login", {"name": data, "uid": uid}, room=sid)
56
57 @sio.event
58 def seek(sid, data):
59 """ When user click on 'Play' button """
60 global connected, searching
61 connected[data["uid"]] = sid
62 if not searching:
63 searching = {"uid": data["uid"], "sid": sid, "name": data["name"]}
64 else:
65 # Active seek pending: create game
66 opponent = searching
67 searching = {}
68 con = sqlite3.connect(DB_PATH)
69 cur = con.cursor()
70 today = (date.today(),)
71 cur.execute("insert into Games (created) values (?)", today)
72 gid = cur.lastrowid
73 # To room == sid, opponent is me. To my room, it's him/her
74 sio.emit("play",
75 {"gid":gid, "oppid":opponent["uid"], "oppname":opponent["name"]},
76 room=sid)
77 sio.emit("play",
78 {"gid":gid, "oppid":data["uid"], "oppname":data["name"]},
79 room=opponent["sid"])
80 id_list = [(data["uid"],gid), (opponent["uid"],gid)]
81 cur.executemany("insert into Players (uid,gid) values (?,?)", id_list)
82 con.commit()
83 con.close()
84
85 @sio.event
86 def move(sid, data):
87 """ New move to DB + transmit to opponent """
88 sio.emit("move", data, room=connected[data["oppid"]])
89 con = sqlite3.connect(DB_PATH)
90 cur = con.cursor()
91 cur.execute("insert into Moves (uid,gid,choice,mnum) values (?,?,?,?)",
92 (data["uid"],data["gid"],data["choice"],data["mnum"]))
93 con.commit()
94 con.close()
95
96 @sio.event
97 def inc_pts(sid, data):
98 """ Add a point to the player (who won last round) """
99 con = sqlite3.connect(DB_PATH)
100 cur = con.cursor()
101 cur.execute("update Players set points=points+1 where uid=? and gid=?",
102 (data["uid"],data["gid"]))
103 con.commit()
104 con.close()
105
106 static_files = {
107 '/': RPSLS_PATH + '/index.html',
108 '/rpsls.js': RPSLS_PATH + '/rpsls.js',
109 '/favicon.ico': RPSLS_PATH + '/favicon.ico',
110 '/assets': RPSLS_PATH + '/assets'
111 }
112
113 PORT = getenv('RPSLS_PORT')
114 if PORT is None:
115 PORT = "8000"
116 PORT = int(PORT)
117
118 # Wrap with a WSGI application
119 app = socketio.WSGIApp(sio, static_files=static_files)
120 eventlet.wsgi.server(eventlet.listen(('127.0.0.1', PORT)), app)