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 | ||
7cc3b851 BA |
20 | # Avoid repeating DB connect/close code |
21 | def db_operation(func): | |
22 | con = sqlite3.connect(DB_PATH) | |
23 | cur = con.cursor() | |
24 | func(cur) | |
25 | con.commit() | |
26 | con.close() | |
27 | ||
bbb90bba BA |
28 | @sio.event |
29 | def disconnect(sid): | |
30 | """ Triggered at page reload or tab close """ | |
31 | global connected, searching | |
32 | try: | |
33 | key_idx = list(connected.values()).index(sid) | |
34 | del connected[list(connected.keys())[key_idx]] | |
35 | except ValueError: | |
36 | # If the user didn't seek, no key to find | |
37 | pass | |
38 | if searching and searching["sid"] == sid: | |
39 | searching = {} | |
40 | ||
41 | @sio.event | |
42 | def login(sid, data): | |
43 | """ When user sends name from /login page """ | |
0d0fffaa | 44 | if not re_match(r"^[a-zA-Z]{3,}$", data): |
bbb90bba BA |
45 | sio.emit("login", {"err": "Name: letters only"}, room=sid) |
46 | return | |
7cc3b851 BA |
47 | def upsert(cur): |
48 | uid = 0 | |
49 | try: | |
50 | # Always try to insert (new) Users row | |
51 | cur.execute("insert into Users (name) values (?)", (data,)) | |
52 | uid = cur.lastrowid | |
53 | except sqlite3.IntegrityError as err: | |
54 | # If fails: user already exists, find its ID | |
55 | if str(err) == "UNIQUE constraint failed: Users.name": | |
56 | cur.execute("select id from Users where name = ?", (data,)) | |
57 | uid = cur.fetchone()[0] | |
58 | else: | |
59 | raise | |
310a5feb | 60 | sio.emit("login", {"name": data, "uid": uid}, room=sid) |
7cc3b851 | 61 | db_operation(upsert) |
bbb90bba BA |
62 | |
63 | @sio.event | |
64 | def seek(sid, data): | |
65 | """ When user click on 'Play' button """ | |
66 | global connected, searching | |
67 | connected[data["uid"]] = sid | |
68 | if not searching: | |
69 | searching = {"uid": data["uid"], "sid": sid, "name": data["name"]} | |
70 | else: | |
71 | # Active seek pending: create game | |
72 | opponent = searching | |
73 | searching = {} | |
7cc3b851 BA |
74 | def create_game(cur): |
75 | today = (date.today(),) | |
76 | cur.execute("insert into Games (created) values (?)", today) | |
77 | gid = cur.lastrowid | |
78 | # To room == sid, opponent is me. To my room, it's him/her | |
79 | sio.emit("play", | |
80 | {"gid":gid, "oppid":opponent["uid"], "oppname":opponent["name"]}, | |
81 | room=sid) | |
82 | sio.emit("play", | |
83 | {"gid":gid, "oppid":data["uid"], "oppname":data["name"]}, | |
84 | room=opponent["sid"]) | |
85 | id_list = [(data["uid"],gid), (opponent["uid"],gid)] | |
86 | cur.executemany("insert into Players (uid,gid) values (?,?)", id_list) | |
87 | db_operation(create_game) | |
bbb90bba BA |
88 | |
89 | @sio.event | |
90 | def move(sid, data): | |
91 | """ New move to DB + transmit to opponent """ | |
92 | sio.emit("move", data, room=connected[data["oppid"]]) | |
7cc3b851 BA |
93 | db_operation(lambda cur: |
94 | cur.execute("insert into Moves (uid,gid,choice,mnum) values (?,?,?,?)", | |
95 | (data["uid"],data["gid"],data["choice"],data["mnum"])) | |
96 | ) | |
bbb90bba | 97 | |
f126a42e BA |
98 | @sio.event |
99 | def inc_pts(sid, data): | |
100 | """ Add a point to the player (who won last round) """ | |
7cc3b851 BA |
101 | db_operation(lambda cur: |
102 | cur.execute("update Players set points=points+1 where uid=? and gid=?", | |
103 | (data["uid"],data["gid"])) | |
104 | ) | |
f126a42e | 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 | 114 | if PORT is None: |
d4e3fa37 | 115 | PORT = "8000" |
bbb90bba BA |
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) |