Commit | Line | Data |
---|---|---|
bbb90bba BA |
1 | import socketio |
2 | import eventlet | |
3 | import sqlite3 | |
0d0fffaa | 4 | from re import match as re_match |
bbb90bba | 5 | from datetime import date |
0d0fffaa BA |
6 | from os import getenv |
7 | from sys import path as sys_path | |
bbb90bba BA |
8 | |
9 | # Create a Socket.IO server (CORS arg required on server, not locally) | |
0d0fffaa | 10 | MODE = getenv('RPSLS_MODE') |
52579c58 BA |
11 | allowed_origin = 'https://rpsls.auder.net' if MODE=='production' else '*' |
12 | sio = socketio.Server(cors_allowed_origins=allowed_origin) | |
bbb90bba | 13 | |
0d0fffaa | 14 | RPSLS_PATH = sys_path[0] |
52579c58 | 15 | DB_PATH = RPSLS_PATH + '/db/rpsls.sqlite' |
bbb90bba BA |
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 """ | |
0d0fffaa | 36 | if not re_match(r"^[a-zA-Z]{3,}$", data): |
bbb90bba BA |
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 | ||
f126a42e BA |
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 | ||
bbb90bba | 106 | static_files = { |
52579c58 BA |
107 | '/': RPSLS_PATH + '/index.html', |
108 | '/rpsls.js': RPSLS_PATH + '/rpsls.js', | |
109 | '/favicon.ico': RPSLS_PATH + '/favicon.ico', | |
110 | '/assets': RPSLS_PATH + '/assets' | |
bbb90bba BA |
111 | } |
112 | ||
0d0fffaa | 113 | PORT = getenv('RPSLS_PORT') |
bbb90bba BA |
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) |