work in progress, commit just for sync
This commit is contained in:
parent
df6b1d8c59
commit
d6b68de008
168
README.md
168
README.md
@ -6,7 +6,7 @@ communication with the web client is done by a (far from any standard and almost
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
**register as player and in game queue:**
|
**register as player:**
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
@ -89,3 +89,169 @@ response:
|
|||||||
```
|
```
|
||||||
|
|
||||||
(response?)
|
(response?)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## new version:
|
||||||
|
|
||||||
|
**json match state:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
complete_field: '[[...],[...],...]',
|
||||||
|
global_field: '[[...],[...],...]',
|
||||||
|
last_move: {
|
||||||
|
"sub_x": "...",
|
||||||
|
"sub_y": "...",
|
||||||
|
"x": "...",
|
||||||
|
"y": "..."
|
||||||
|
}
|
||||||
|
game_over: <true | false>,
|
||||||
|
player_won: <null | <player_name>>
|
||||||
|
current_player: <null | <player_name>>
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**new temp session**
|
||||||
|
|
||||||
|
client
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "temp_session",
|
||||||
|
"data": {
|
||||||
|
"name": "<player_name>"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
server response:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "temp_session_response",
|
||||||
|
"data": {
|
||||||
|
"success": <true|false>,
|
||||||
|
"id": "<session-id>",
|
||||||
|
"message": "..."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**connect by session id**
|
||||||
|
|
||||||
|
client
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "reconnect",
|
||||||
|
"data": {
|
||||||
|
"id": "<session-id>",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
server response:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "reconnect_response",
|
||||||
|
"data": {
|
||||||
|
"success": <true|false>,
|
||||||
|
"msg": "..."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**register**:
|
||||||
|
|
||||||
|
TODO
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
**match_request**:
|
||||||
|
|
||||||
|
client
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "match_request",
|
||||||
|
"data": {
|
||||||
|
"player": <null | <opponent_name>>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
server_response:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "match_request_response",
|
||||||
|
"data": {
|
||||||
|
"success": <true|false>
|
||||||
|
"msg": "..."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
**match_move**:
|
||||||
|
|
||||||
|
client
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "move",
|
||||||
|
"data": {
|
||||||
|
"sub_x": "...",
|
||||||
|
"sub_y": "...",
|
||||||
|
"x": "...",
|
||||||
|
"y": "..."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
server response: (maybe useless?)
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "move_response",
|
||||||
|
"data": {
|
||||||
|
"success": true,
|
||||||
|
"msg": "..."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
**match update**
|
||||||
|
|
||||||
|
(also send on match start and send for all matches after login)
|
||||||
|
|
||||||
|
server:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "match_update",
|
||||||
|
"data": {
|
||||||
|
"id": "<match_id>",
|
||||||
|
"match_state": "<json match state>"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**match close**
|
||||||
|
|
||||||
|
client:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "end_match",
|
||||||
|
"data": {
|
||||||
|
"id": "<match_id>"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
227
connection_handler.py
Normal file
227
connection_handler.py
Normal file
@ -0,0 +1,227 @@
|
|||||||
|
import asyncio
|
||||||
|
import websockets
|
||||||
|
import json
|
||||||
|
from session_manager import SessionManager
|
||||||
|
from user_manager import UserManager
|
||||||
|
from match_manager import MatchManager
|
||||||
|
from match import Match
|
||||||
|
|
||||||
|
|
||||||
|
def parse_message(msg: str):
|
||||||
|
# TODO: make it more robust by validating each part of a message
|
||||||
|
msg_obj = json.loads(msg)
|
||||||
|
if "type" not in msg_obj or "data" not in msg_obj:
|
||||||
|
print("got strange message")
|
||||||
|
return None
|
||||||
|
|
||||||
|
return msg_obj
|
||||||
|
|
||||||
|
|
||||||
|
class Connection(object):
|
||||||
|
def __init__(self,
|
||||||
|
id,
|
||||||
|
user_name: str,
|
||||||
|
registered: bool,
|
||||||
|
websocket: websocket.WebSocketServerProtocol):
|
||||||
|
self.id = id
|
||||||
|
self.user_name = user_name
|
||||||
|
self.websocket = websocket
|
||||||
|
self.is_registered_user = registered
|
||||||
|
|
||||||
|
|
||||||
|
class ConnectionHandler(object):
|
||||||
|
def __init__(self,
|
||||||
|
session_manager: SessionManager,
|
||||||
|
user_manager: UserManager,
|
||||||
|
match_manager: MatchManager):
|
||||||
|
self.session_manager = session_manager
|
||||||
|
self.user_manager = user_manager
|
||||||
|
self.match_manager = match_manager
|
||||||
|
|
||||||
|
self.open_connections_by_user = {}
|
||||||
|
self.open_connections_by_id = {}
|
||||||
|
|
||||||
|
self.match_queue = set()
|
||||||
|
|
||||||
|
def reconnect_session(self,
|
||||||
|
socket: websockets.WebSocketServerProtocol,
|
||||||
|
session_id: str):
|
||||||
|
# check whether id exists
|
||||||
|
tmp = self.session_manager.get_session_by_id(session_id)
|
||||||
|
if len(tmp) == 0:
|
||||||
|
# session not available!
|
||||||
|
return None
|
||||||
|
|
||||||
|
session_obj = tmp[0]
|
||||||
|
|
||||||
|
is_registerd = session_obj['registered_user'] is not None
|
||||||
|
|
||||||
|
user_name = session_obj['registered_user'] if is_registerd else session_obj['temp_user']
|
||||||
|
|
||||||
|
conn = Connection(id=session_id,
|
||||||
|
user_name=user_name,
|
||||||
|
registered=is_registerd,
|
||||||
|
websocket=socket)
|
||||||
|
|
||||||
|
self.session_manager.touch_session(session_id)
|
||||||
|
|
||||||
|
return conn
|
||||||
|
|
||||||
|
def close_connection(self, conn: Connection):
|
||||||
|
if conn.id in self.open_connections_by_id:
|
||||||
|
del(self.open_connections_by_id[conn.id])
|
||||||
|
del(self.open_connections_by_user[conn.user_name])
|
||||||
|
conn.websocket.close()
|
||||||
|
|
||||||
|
def close_session(self, id):
|
||||||
|
tmp = self.session_manager.get_session_by_id(id)
|
||||||
|
if len(tmp) == 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
if id in self.open_connections_by_id:
|
||||||
|
self.close_connection([id])
|
||||||
|
|
||||||
|
self.session_manager.delete_session(id)
|
||||||
|
|
||||||
|
def _add_connection(self, conn):
|
||||||
|
self.open_connections_by_id[conn.id] = conn
|
||||||
|
self.open_connections_by_user[conn.user] = conn
|
||||||
|
|
||||||
|
def _del_connection(self, conn):
|
||||||
|
del(self.open_connections_by_id[conn.id])
|
||||||
|
del(self.open_connections_by_user[conn.user])
|
||||||
|
|
||||||
|
async def new_connection(self,
|
||||||
|
socket: websockets.WebSocketServerProtocol,
|
||||||
|
login_msg: str):
|
||||||
|
|
||||||
|
msg = parse_message(login_msg)
|
||||||
|
|
||||||
|
if msg is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if msg['type'] == 'reconnect':
|
||||||
|
conn = self.reconnect_session(socket, msg['data']['id'])
|
||||||
|
if conn is not None:
|
||||||
|
self._add_connection(conn)
|
||||||
|
await conn.websocket.send(json.dumps({
|
||||||
|
"type": "reconnect_response",
|
||||||
|
"data": {
|
||||||
|
"success": True,
|
||||||
|
"msg": ""
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
return conn
|
||||||
|
await conn.websocket.send(json.dumps({
|
||||||
|
"type": "reconnect_response",
|
||||||
|
"data": {
|
||||||
|
"success": False,
|
||||||
|
"msg": "session not available"
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
return None
|
||||||
|
|
||||||
|
elif msg['type'] == 'temp_session':
|
||||||
|
name = msg['data']['name']
|
||||||
|
if len(self.session_manager.get_session_by_temp_user(name)) == 0:
|
||||||
|
if len(self.user_manager.get_user(name)) == 0:
|
||||||
|
if len(msg['data']['name']) < 16 and ';' not in name and '\'' not in name and '\"' not in name:
|
||||||
|
id = self.session_manager.create_session_for_temp_user(
|
||||||
|
name)
|
||||||
|
conn = Connection(
|
||||||
|
id=id, user_name=name, registered=False, websocket=socket)
|
||||||
|
self._add_connection(conn)
|
||||||
|
await socket.send(json.dumps({
|
||||||
|
"type": "temp_session_response",
|
||||||
|
"data": {
|
||||||
|
"success": True,
|
||||||
|
"id": id,
|
||||||
|
"message": "logged in as temporary user " + name
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
return conn
|
||||||
|
|
||||||
|
await socket.send(json.dumps({
|
||||||
|
"type": "temp_session_response",
|
||||||
|
"data": {
|
||||||
|
"success": False,
|
||||||
|
"id": None,
|
||||||
|
"message": "user name not available"
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
return None
|
||||||
|
|
||||||
|
elif msg['type'] == 'login':
|
||||||
|
# TODO
|
||||||
|
pass
|
||||||
|
|
||||||
|
elif msg['type'] == 'register':
|
||||||
|
# TODO
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def _start_match(self, user_a, user_b):
|
||||||
|
m = self.match_manager.create_new_match(user_a, user_b)
|
||||||
|
state = m.to_json_state()
|
||||||
|
if user_a in self.open_connections_by_user:
|
||||||
|
await self.open_connections_by_user[user_a].websocket.send(
|
||||||
|
json.dumps({
|
||||||
|
{
|
||||||
|
"type": "match_update",
|
||||||
|
"data": {
|
||||||
|
"id": m.id,
|
||||||
|
"match_state": state
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
if user_b in self.open_connections_by_user:
|
||||||
|
await self.open_connections_by_user[user_b].websocket.send(
|
||||||
|
json.dumps({
|
||||||
|
{
|
||||||
|
"type": "match_update",
|
||||||
|
"data": {
|
||||||
|
"id": m.id,
|
||||||
|
"match_state": state
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
async def _on_match_req(self, conn, data):
|
||||||
|
if len(self.match_queue) > 0:
|
||||||
|
# it's a match!
|
||||||
|
user_a = self.match_queue.pop()
|
||||||
|
self._start_match(user_a, conn.user_name)
|
||||||
|
|
||||||
|
else:
|
||||||
|
self.match_queue.append(conn.user_name)
|
||||||
|
|
||||||
|
async def _on_match_move(self, conn, data):
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def _on_match_close(self, conn, data):
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def disconnect(self, conn):
|
||||||
|
self._del_connection(conn)
|
||||||
|
|
||||||
|
async def handle_message(self, conn, msg_str):
|
||||||
|
msg = parse_message(msg_str)
|
||||||
|
|
||||||
|
if msg is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
t = msg['type']
|
||||||
|
|
||||||
|
self.user_manager.touch_user(conn.user_name)
|
||||||
|
if t == "match_request":
|
||||||
|
await self._on_match_req(conn, msg['data'])
|
||||||
|
|
||||||
|
elif t == "move":
|
||||||
|
await self._on_match_move(conn, msg['data'])
|
||||||
|
|
||||||
|
elif t == "end_match":
|
||||||
|
await self._on_match_close(conn, msg['data'])
|
||||||
|
|
||||||
|
else:
|
||||||
|
print("could not interpret message: " + msg_str)
|
33
create_database.py
Executable file
33
create_database.py
Executable file
@ -0,0 +1,33 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# this script creates the necessary tables in the databse
|
||||||
|
|
||||||
|
import settings
|
||||||
|
from database_connection import DatabaseConnection
|
||||||
|
|
||||||
|
|
||||||
|
def create_tables():
|
||||||
|
|
||||||
|
DatabaseConnection(settings.db_host,
|
||||||
|
settings.db_port,
|
||||||
|
settings.db_user,
|
||||||
|
settings.db_pw,
|
||||||
|
settings.db_db,
|
||||||
|
settings.db_charset)
|
||||||
|
|
||||||
|
queries = [
|
||||||
|
"DROP TABLE IF EXISTS matches",
|
||||||
|
"DROP TABLE IF EXISTS sessions",
|
||||||
|
"DROP TABLE IF EXISTS users",
|
||||||
|
"CREATE TABLE users (name varchar(16) NOT NULL, pw_hash varchar(128) NOT NULL, pw_salt varchar(32) NOT NULL, last_seen datetime NOT NULL, PRIMARY KEY (name)) CHARACTER SET " + settings.db_charset,
|
||||||
|
"CREATE TABLE matches (id varchar(32) NOT NULL, user_a varchar(16) NOT NULL, user_b varchar(16) NOT NULL, match_state varchar(4096) NOT NULL, active_user varchar(16), last_active datetime NOT NULL, FOREIGN KEY (user_a) REFERENCES users(name), FOREIGN KEY (user_b) REFERENCES users(name), FOREIGN KEY (active_user) REFERENCES users(name)) CHARACTER SET " + settings.db_charset,
|
||||||
|
"CREATE TABLE sessions (id varchar(32) NOT NULL, registered_user varchar(16), temp_user varchar(16), last_seen datetime NOT NULL, PRIMARY KEY (id), FOREIGN KEY(registered_user) REFERENCES users(name)) CHARACTER SET " + settings.db_charset
|
||||||
|
]
|
||||||
|
|
||||||
|
for query in queries:
|
||||||
|
DatabaseConnection.global_single_execution(query)
|
||||||
|
|
||||||
|
DatabaseConnection.global_close()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
create_tables()
|
106
database_connection.py
Normal file
106
database_connection.py
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
import pymysql.cursors
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
def get_sql_time(datetime_object):
|
||||||
|
return datetime_object.strftime('%Y-%m-%d %H:%M:%S')
|
||||||
|
|
||||||
|
|
||||||
|
class SQLInjectionError(Exception):
|
||||||
|
def __init__(self):
|
||||||
|
|
||||||
|
# Call the base class constructor with the parameters it needs
|
||||||
|
super().__init__("Detected possible SQL injection attack!")
|
||||||
|
|
||||||
|
|
||||||
|
class DatabaseConnection(object):
|
||||||
|
"""
|
||||||
|
a singleton class for a global database connection
|
||||||
|
"""
|
||||||
|
|
||||||
|
instance = None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def global_cursor():
|
||||||
|
assert DatabaseConnection.instance is not None
|
||||||
|
return DatabaseConnection.instance.get_cursor()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def global_close():
|
||||||
|
assert DatabaseConnection.instance is not None
|
||||||
|
DatabaseConnection.instance.close()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def global_commit():
|
||||||
|
assert DatabaseConnection.instance is not None
|
||||||
|
DatabaseConnection.instance.commit()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def global_single_query(query):
|
||||||
|
if ';' in query:
|
||||||
|
# Possible injection!
|
||||||
|
raise SQLInjectionError()
|
||||||
|
|
||||||
|
with DatabaseConnection.global_cursor() as c:
|
||||||
|
c.execute(query)
|
||||||
|
return c.fetchall()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def global_single_execution(sql_statement):
|
||||||
|
if ';' in sql_statement:
|
||||||
|
# Possible injection detected!
|
||||||
|
raise SQLInjectionError()
|
||||||
|
|
||||||
|
with DatabaseConnection.global_cursor() as c:
|
||||||
|
c.execute(sql_statement)
|
||||||
|
DatabaseConnection.global_commit()
|
||||||
|
|
||||||
|
def __init__(self,
|
||||||
|
host: str,
|
||||||
|
port: int,
|
||||||
|
user: str,
|
||||||
|
password: str,
|
||||||
|
db: str,
|
||||||
|
charset: str):
|
||||||
|
|
||||||
|
assert DatabaseConnection.instance is None
|
||||||
|
try:
|
||||||
|
self.connection = pymysql.connect(
|
||||||
|
host=host,
|
||||||
|
port=port,
|
||||||
|
user=user,
|
||||||
|
password=password,
|
||||||
|
db=db,
|
||||||
|
charset=charset,
|
||||||
|
cursorclass=pymysql.cursors.DictCursor)
|
||||||
|
DatabaseConnection.instance = self
|
||||||
|
except Exception as e:
|
||||||
|
sys.stderr.write("could not connect to database '" +
|
||||||
|
str(db) +
|
||||||
|
"' at " +
|
||||||
|
user +
|
||||||
|
"@" +
|
||||||
|
host +
|
||||||
|
":" +
|
||||||
|
str(port) +
|
||||||
|
"\nCheck the configuration in settings.py!\n")
|
||||||
|
raise Exception('could not connec to database')
|
||||||
|
|
||||||
|
def get_cursor(self):
|
||||||
|
return self.connection.cursor()
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
self.connection.close()
|
||||||
|
DatabaseConnection.instance = None
|
||||||
|
|
||||||
|
def commit(self):
|
||||||
|
self.connection.commit()
|
||||||
|
|
||||||
|
def test_connection():
|
||||||
|
import settings
|
||||||
|
DatabaseConnection(settings.db_host,
|
||||||
|
settings.db_port,
|
||||||
|
settings.db_user,
|
||||||
|
settings.db_pw,
|
||||||
|
settings.db_db,
|
||||||
|
settings.db_charset)
|
2
main.py
2
main.py
@ -64,7 +64,7 @@ async def socket_worker(websocket, path):
|
|||||||
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
|
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
|
||||||
ssl_context.load_cert_chain(cert_file , keyfile=key_file)
|
ssl_context.load_cert_chain(cert_file , keyfile=key_file)
|
||||||
|
|
||||||
start_server = websockets.serve(socket_worker, host='', port=5555, ssl=ssl_context)
|
start_server = websockets.serve(socket_worker, host='', port=server_port, ssl=ssl_context)
|
||||||
|
|
||||||
asyncio.get_event_loop().run_until_complete(start_server)
|
asyncio.get_event_loop().run_until_complete(start_server)
|
||||||
asyncio.get_event_loop().run_forever()
|
asyncio.get_event_loop().run_forever()
|
||||||
|
188
match.py
Normal file
188
match.py
Normal file
@ -0,0 +1,188 @@
|
|||||||
|
|
||||||
|
import numpy as np
|
||||||
|
import json
|
||||||
|
import base64
|
||||||
|
|
||||||
|
# decoder/encoder took from: https://stackoverflow.com/a/19271311
|
||||||
|
|
||||||
|
|
||||||
|
def Base64Encode(ndarray):
|
||||||
|
return json.dumps([str(ndarray.dtype), base64.b64encode(ndarray), ndarray.shape])
|
||||||
|
|
||||||
|
|
||||||
|
def Base64Decode(jsonDump):
|
||||||
|
loaded = json.loads(jsonDump)
|
||||||
|
dtype = np.dtype(loaded[0])
|
||||||
|
arr = np.frombuffer(base64.decodestring(loaded[1]), dtype)
|
||||||
|
if len(loaded) > 2:
|
||||||
|
return arr.reshape(loaded[2])
|
||||||
|
return arr
|
||||||
|
|
||||||
|
|
||||||
|
def SimpleEncode(ndarray):
|
||||||
|
return json.dumps(ndarray.tolist())
|
||||||
|
|
||||||
|
|
||||||
|
def SimpleDecode(jsonDump):
|
||||||
|
return np.array(json.loads(jsonDump))
|
||||||
|
|
||||||
|
|
||||||
|
FIELD_EMPTY = 0
|
||||||
|
FIELD_USER_A = 1
|
||||||
|
FIELD_USER_B = 2
|
||||||
|
FIELD_DRAW = 3
|
||||||
|
|
||||||
|
|
||||||
|
class Match(object):
|
||||||
|
def __init__(self, n, match_id, player_a_name, player_b_name, json_state=None):
|
||||||
|
self.n = n
|
||||||
|
self.id = match_id
|
||||||
|
self.complete_field = np.zeros(shape=(n*n, n*n), dtype=int)
|
||||||
|
self.global_field = np.zeros(shape=(n, n), dtype=int)
|
||||||
|
self.player_won = None
|
||||||
|
self.game_over = False
|
||||||
|
self.last_move = None
|
||||||
|
self.is_player_a = True
|
||||||
|
self.player_a_name = player_a_name
|
||||||
|
self.player_b_name = player_b_name
|
||||||
|
|
||||||
|
if json_state is not None:
|
||||||
|
self.from_json_state(json_state)
|
||||||
|
|
||||||
|
def from_json_state(self, json_state):
|
||||||
|
match_obj = json.loads(json_state)
|
||||||
|
self.complete_field = np.array(match_obj['complete_field'], dtype=int)
|
||||||
|
self.global_field = np.array(match_obj['global_field'], dtype=int)
|
||||||
|
self.player_won = match_obj['player_won']
|
||||||
|
self.game_over = match_obj['game_over']
|
||||||
|
self.last_move = match_obj['last_move']
|
||||||
|
self.is_player_a = match_obj['active_player'] == self.player_a_name
|
||||||
|
|
||||||
|
def to_json_state(self):
|
||||||
|
match_obj = {
|
||||||
|
'complete_field': self.complete_field.tolist(),
|
||||||
|
'global_field': self.global_field.tolist(),
|
||||||
|
'last_move': self.last_move,
|
||||||
|
'game_over': self.game_over,
|
||||||
|
'player_won': self.player_won,
|
||||||
|
'active_player': self.player_a_name if self.is_player_a else self.player_b_name
|
||||||
|
}
|
||||||
|
|
||||||
|
return json.dumps(match_obj)
|
||||||
|
|
||||||
|
def switch_player_names(self):
|
||||||
|
tmp = self.player_a_name
|
||||||
|
self.player_a_name = self.player_b_name
|
||||||
|
self.player_b_name = tmp
|
||||||
|
|
||||||
|
def get_current_player(self):
|
||||||
|
return self.player_a_name if self.is_player_a else self.player_b_name
|
||||||
|
|
||||||
|
def is_move_valid(self, sub_x, sub_y, x, y):
|
||||||
|
if sub_x < 0 or sub_x >= self.n:
|
||||||
|
return False
|
||||||
|
if sub_y < 0 or sub_y >= self.n:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if x < 0 or x >= self.n:
|
||||||
|
return False
|
||||||
|
if y < 0 or y >= self.n:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if (self.last_move is not None):
|
||||||
|
last_x = self.last_move['x']
|
||||||
|
last_y = self.last_move['y']
|
||||||
|
last_sub_x = self.last_move['sub_x']
|
||||||
|
last_sub_y = self.last_move['sub_y']
|
||||||
|
|
||||||
|
if sub_x != last_x and self.global_field[last_sub_y, last_sub_x] == FIELD_EMPTY:
|
||||||
|
# user is not allowed to place everywhere! wrong move!
|
||||||
|
return False
|
||||||
|
|
||||||
|
if sub_y != last_y and self.global_field[last_sub_y, last_sub_x] == FIELD_EMPTY:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if self.complete_field[sub_y * self.n + y][sub_x * self.n + x] != FIELD_EMPTY:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def is_full(self, field):
|
||||||
|
return not field.__contains__(FIELD_EMPTY)
|
||||||
|
|
||||||
|
def check_win(self, field, x, y):
|
||||||
|
is_col = True
|
||||||
|
is_row = True
|
||||||
|
is_main_diag = False
|
||||||
|
is_sec_diag = False
|
||||||
|
|
||||||
|
val = field[y, x]
|
||||||
|
|
||||||
|
for i in range(self.n):
|
||||||
|
if (field[i, x] != val):
|
||||||
|
is_col = False
|
||||||
|
break
|
||||||
|
|
||||||
|
for i in range(self.n):
|
||||||
|
if (field[y, i] != val):
|
||||||
|
is_row = False
|
||||||
|
break
|
||||||
|
|
||||||
|
if x == y:
|
||||||
|
is_main_diag = True
|
||||||
|
for i in range(self.n):
|
||||||
|
if field[i, i] != val:
|
||||||
|
is_main_diag = False
|
||||||
|
break
|
||||||
|
|
||||||
|
if x + y == self.n - 1:
|
||||||
|
is_sec_diag = True
|
||||||
|
for i in range(self.n):
|
||||||
|
if field[i, -i] != val:
|
||||||
|
is_sec_diag = False
|
||||||
|
break
|
||||||
|
|
||||||
|
return is_col or is_row or is_main_diag or is_sec_diag
|
||||||
|
|
||||||
|
def move(self, move_dict):
|
||||||
|
sub_x = move_dict['sub_x']
|
||||||
|
sub_y = move_dict['sub_y']
|
||||||
|
x = move_dict['x']
|
||||||
|
y = move_dict['y']
|
||||||
|
|
||||||
|
abs_x = sub_x * self.n + x
|
||||||
|
abs_y = sub_y * self.n + y
|
||||||
|
|
||||||
|
player_mark = FIELD_USER_A if self.is_player_a else FIELD_USER_B
|
||||||
|
|
||||||
|
if not self.is_move_valid(sub_x, sub_y, x, y):
|
||||||
|
return False
|
||||||
|
|
||||||
|
# else: move!
|
||||||
|
self.complete_field[abs_y, abs_x] = player_mark
|
||||||
|
|
||||||
|
# encode move:
|
||||||
|
self.last_move = move_dict
|
||||||
|
|
||||||
|
# check whether this indicates changes in the global field:
|
||||||
|
assert self.global_field[sub_y, sub_x] == FIELD_EMPTY
|
||||||
|
|
||||||
|
subgrid = self.complete_field[sub_y * self.n: (
|
||||||
|
sub_y + 1) * self.n, sub_x * self.n: (sub_x + 1) * self.n]
|
||||||
|
|
||||||
|
if self.check_win(subgrid, x, y):
|
||||||
|
self.global_field[sub_x, sub_y] = player_mark
|
||||||
|
|
||||||
|
elif self.is_full(subgrid):
|
||||||
|
self.global_field[sub_x, sub_y] = FIELD_DRAW
|
||||||
|
|
||||||
|
# check global state:
|
||||||
|
if self.check_win(self.global_field, sub_x, sub_y):
|
||||||
|
self.game_over = True
|
||||||
|
self.player_won = self.player_a_name if self.is_player_a else self.player_b_name
|
||||||
|
|
||||||
|
elif self.is_full(self.global_field):
|
||||||
|
self.game_over = True
|
||||||
|
self.player_won = None
|
||||||
|
|
||||||
|
self.is_player_a = not self.is_player_a
|
||||||
|
|
||||||
|
return True
|
46
match_manager.py
Normal file
46
match_manager.py
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
from database_connection import DatabaseConnection, SQLInjectionError, get_sql_time
|
||||||
|
from user import User
|
||||||
|
import datetime
|
||||||
|
import uuid
|
||||||
|
import settings
|
||||||
|
from match import Match
|
||||||
|
|
||||||
|
|
||||||
|
class MatchManager(object):
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def get_match(self, id):
|
||||||
|
query = f"SELECT * FROM matches WHERE id='{id}'"
|
||||||
|
result = DatabaseConnection.global_single_query(query)
|
||||||
|
if len(result) == 0:
|
||||||
|
return None
|
||||||
|
match = Match(n=settings.n, match_id=id, player_a_name=result[0]['user_a'],
|
||||||
|
player_b_name=result[0]['user_b'], json_state=result[0]['match_state'])
|
||||||
|
return match
|
||||||
|
|
||||||
|
def get_matches_for_user(self, user_name):
|
||||||
|
query = f"SELECT * FROM matches WHERE user_a='{user_name}' OR user_b='{user_name}'"
|
||||||
|
return DatabaseConnection.global_single_query(query)
|
||||||
|
|
||||||
|
def create_new_match(self, user_a, user_b):
|
||||||
|
match_id = uuid.uuid4().hex
|
||||||
|
# check if already existent (but should not be the case)
|
||||||
|
if len(DatabaseConnection.global_single_query(f"SELECT id FROM matches WHERE id='{match_id}'")) > 0:
|
||||||
|
return self.create_new_match(user_a, user_b)
|
||||||
|
|
||||||
|
match = Match(n=settings.n, match_id=match_id, player_a_name=user_a, player_b_name=user_b)
|
||||||
|
now = datetime.datetime.now()
|
||||||
|
query = f"INSERT INTO matches (id, user_a, user_b, match_state, active_user, last_active) VALUES ('{match_id}', '{user_a}', '{user_b}', '{match.to_json_state()}', '{match.get_current_player()}','{get_sql_time(now)}')"
|
||||||
|
DatabaseConnection.global_single_execution(query)
|
||||||
|
return match
|
||||||
|
|
||||||
|
def update_match(self, match_id, match):
|
||||||
|
now = get_sql_time(datetime.datetime.now())
|
||||||
|
query = f"UPDATE matches SET match_state='{match.to_json_state()}', active_user='{match.get_current_player()}', last_active='{now}' WHERE id='{match_id}'"
|
||||||
|
DatabaseConnection.global_single_execution(query)
|
||||||
|
|
||||||
|
def delete_match(self, match_id):
|
||||||
|
query = f"DELETE FROM matches WHERE id='{match_id}'"
|
||||||
|
DatabaseConnection.global_single_execution(query)
|
||||||
|
|
1
message_handler.py
Normal file
1
message_handler.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
|
59
session_manager.py
Normal file
59
session_manager.py
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
from database_connection import DatabaseConnection, SQLInjectionError, get_sql_time
|
||||||
|
from user import User
|
||||||
|
import datetime
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
|
||||||
|
class SessionManager(object):
|
||||||
|
def __init__(self, session_lifespan_timedelta):
|
||||||
|
self.session_lifespan_timedelta = session_lifespan_timedelta
|
||||||
|
|
||||||
|
def get_session_by_id(self, session_id):
|
||||||
|
query = f"SELECT * FROM sessions WHERE id='{session_id}'"
|
||||||
|
return DatabaseConnection.global_single_query(query)
|
||||||
|
|
||||||
|
def touch_session(self, session_id):
|
||||||
|
query = f"UPDATE sessions SET last_seen='{datetime.datetime.now()}' WHERE id='{session_id}'"
|
||||||
|
DatabaseConnection.global_single_execution(query)
|
||||||
|
|
||||||
|
def get_session_by_registered_user(self, user_name):
|
||||||
|
query = f"SELECT * FROM sessions WHERE registered_user='{user_name}'"
|
||||||
|
return DatabaseConnection.global_single_query(query)
|
||||||
|
|
||||||
|
def get_session_by_temp_user(self, user_name):
|
||||||
|
query = f"SELECT * FROM sessions WHERE temp_user='{user_name}'"
|
||||||
|
return DatabaseConnection.global_single_query(query)
|
||||||
|
|
||||||
|
def create_session_for_registered_user(self, user_name):
|
||||||
|
new_id = uuid.uuid4().hex
|
||||||
|
# check if already existent (but should not be the case)
|
||||||
|
if len(DatabaseConnection.global_single_query(f"SELECT id FROM sessions WHERE id='{new_id}'")) > 0:
|
||||||
|
# okay, next try:
|
||||||
|
return self.create_session_for_registered_user(self, user_name)
|
||||||
|
|
||||||
|
query = f"INSERT INTO sessions (id, registered_user, temp_user, last_seen) VALUES ( '{new_id}', '{user_name}', NULL, '{datetime.datetime.now()}')"
|
||||||
|
DatabaseConnection.global_single_execution(query)
|
||||||
|
return new_id
|
||||||
|
|
||||||
|
def create_session_for_temp_user(self, user_name):
|
||||||
|
new_id = uuid.uuid4().hex
|
||||||
|
# check if already existent (but should not be the case)
|
||||||
|
if len(DatabaseConnection.global_single_query(f"SELECT id FROM sessions WHERE id='{new_id}'")) > 0:
|
||||||
|
# okay, next try:
|
||||||
|
return self.create_session_for_registered_user(self, user_name)
|
||||||
|
|
||||||
|
query = f"INSERT INTO sessions (id, registered_user, temp_user, last_seen) VALUES ( '{new_id}', NULL, '{user_name}', '{datetime.datetime.now()}')"
|
||||||
|
DatabaseConnection.global_single_execution(query)
|
||||||
|
return new_id
|
||||||
|
|
||||||
|
def delete_session(self, session_id):
|
||||||
|
query = f"DELETE FROM sessions WHERE id='{session_id}'"
|
||||||
|
DatabaseConnection.global_single_execution(query)
|
||||||
|
|
||||||
|
def revoke_inactive_sessions(self):
|
||||||
|
revoke_time = datetime.datetime.now() - self.session_lifespan_timedelta
|
||||||
|
query = f"SELECT * from sessions WHERE last_seen < '{get_sql_time(revoke_time)}'"
|
||||||
|
revoked_sessions = DatabaseConnection.global_single_query(query)
|
||||||
|
query = f"DELETE FROM sessions WHERE last_seen < '{get_sql_time(revoke_time)}'"
|
||||||
|
DatabaseConnection.global_single_execution(query)
|
||||||
|
return revoked_sessions
|
10
user.py
Normal file
10
user.py
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
class User(object):
|
||||||
|
def __init__(self, id, name):
|
||||||
|
self.id = id
|
||||||
|
self.name = name
|
||||||
|
|
||||||
|
def get_id(self):
|
||||||
|
return self.id
|
||||||
|
|
||||||
|
def get_name(self):
|
||||||
|
return self.name
|
65
user_manager.py
Normal file
65
user_manager.py
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
from database_connection import DatabaseConnection, SQLInjectionError
|
||||||
|
from user import User
|
||||||
|
import datetime
|
||||||
|
import hashlib
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
|
||||||
|
class UserManager(object):
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def get_user(self, user_name):
|
||||||
|
query = f"SELECT name, last_seen FROM users where name='{user_name}'"
|
||||||
|
return DatabaseConnection.global_single_query(query)
|
||||||
|
|
||||||
|
def delete_user(self, user_name):
|
||||||
|
query = f"DELETE FROM users where name='{user_name}'"
|
||||||
|
DatabaseConnection.global_single_execution(query)
|
||||||
|
|
||||||
|
def verify_user(self, user_name, pw):
|
||||||
|
query = f"SELECT * FROM users where name='{user_name}'"
|
||||||
|
users = DatabaseConnection.global_single_query(query)
|
||||||
|
if len(self.get_user(user_name)) == 0:
|
||||||
|
return False
|
||||||
|
|
||||||
|
user = users[0]
|
||||||
|
pw_salt = user['pw_salt']
|
||||||
|
stored_pw_hash = user['pw_hash']
|
||||||
|
|
||||||
|
pw_hash = hashlib.sha512(pw.encode() + pw_salt.encode()).hexdigest()
|
||||||
|
return stored_pw_hash == pw_hash
|
||||||
|
|
||||||
|
def create_user(self, user_name, pw):
|
||||||
|
assert len(self.get_user(user_name)) == 0
|
||||||
|
pw_salt = uuid.uuid4().hex
|
||||||
|
pw_hash = hashlib.sha512(pw.encode() + pw_salt.encode()).hexdigest()
|
||||||
|
query = f"INSERT INTO users (name, pw_hash, pw_salt, last_seen) VALUES ( '{user_name}', '{pw_hash}', '{pw_salt}', '{datetime.datetime.now()}')"
|
||||||
|
DatabaseConnection.global_single_execution(query)
|
||||||
|
|
||||||
|
def touch_user(self, user_name):
|
||||||
|
matches = self.get_user(user_name)
|
||||||
|
assert len(matches) == 1
|
||||||
|
query = f"UPDATE users SET last_seen='{datetime.datetime.now()}' WHERE name='{user_name}'"
|
||||||
|
DatabaseConnection.global_single_execution(query)
|
||||||
|
|
||||||
|
def get_all_users(self):
|
||||||
|
query = "SELECT name, last_seen FROM users"
|
||||||
|
return DatabaseConnection.global_single_query(query)
|
||||||
|
|
||||||
|
|
||||||
|
def create_test_manager():
|
||||||
|
import settings
|
||||||
|
DatabaseConnection(settings.db_host,
|
||||||
|
settings.db_port,
|
||||||
|
settings.db_user,
|
||||||
|
settings.db_pw,
|
||||||
|
settings.db_db,
|
||||||
|
settings.db_charset)
|
||||||
|
um = UserManager()
|
||||||
|
return um
|
||||||
|
|
||||||
|
|
||||||
|
# test routines:
|
||||||
|
if __name__ == "__main__":
|
||||||
|
pass
|
Loading…
Reference in New Issue
Block a user