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