2019-03-14 00:01:02 +01:00
from database_connection import DatabaseConnection , SQLInjectionError , get_sql_time
2019-03-29 12:52:03 +01:00
from user_manager import UserManager
2019-03-14 00:01:02 +01:00
from user import User
import datetime
import uuid
import settings
from match import Match
2019-03-29 12:52:03 +01:00
from tools import debug , elo_p_win , elo_update
2019-03-27 17:23:04 +01:00
2019-03-14 00:01:02 +01:00
class MatchManager ( object ) :
2019-04-01 16:13:44 +02:00
def __init__ ( self , user_manager : UserManager , match_lifespan_timedelta : datetime . timedelta ) :
2019-03-29 12:52:03 +01:00
self . user_manager = user_manager
2019-04-01 16:13:44 +02:00
self . match_lifespan_timedelta = match_lifespan_timedelta
2019-03-14 00:01:02 +01:00
2019-03-29 12:52:03 +01:00
def get_match ( self , id : str ) - > Match :
2019-03-27 17:23:04 +01:00
query = " SELECT * FROM matches WHERE id= %s "
result = DatabaseConnection . global_single_query ( query , ( id ) )
2019-03-14 00:01:02 +01:00
if len ( result ) == 0 :
return None
2019-04-01 16:13:44 +02:00
revoke_time = result [ 0 ] [ ' last_active ' ] + self . match_lifespan_timedelta
match = Match ( n = settings . n , match_id = id , revoke_time = revoke_time , player_a_name = result [ 0 ] [ ' user_a ' ] ,
2019-04-04 13:44:48 +02:00
player_b_name = result [ 0 ] [ ' user_b ' ] , json_state = result [ 0 ] [ ' match_state ' ] , ranked = result [ 0 ] [ ' ranked ' ] )
2019-03-14 00:01:02 +01:00
return match
2019-03-29 12:52:03 +01:00
def get_matches_for_user ( self , user_name : str ) - > list :
2019-03-27 17:23:04 +01:00
query = " SELECT * FROM matches WHERE user_a= %s OR user_b= %s "
return DatabaseConnection . global_single_query ( query , ( user_name , user_name ) )
2019-03-14 00:01:02 +01:00
2019-03-29 12:52:03 +01:00
def create_new_match ( self , user_a : str , user_b : str ) - > Match :
2019-03-14 00:01:02 +01:00
match_id = uuid . uuid4 ( ) . hex
# check if already existent (but should not be the case)
2019-03-27 17:23:04 +01:00
if len ( DatabaseConnection . global_single_query ( " SELECT id FROM matches WHERE id= %s " , ( match_id ) ) ) > 0 :
2019-03-14 00:01:02 +01:00
return self . create_new_match ( user_a , user_b )
2019-04-04 13:44:48 +02:00
ranked = len ( self . user_manager . get_user ( user_a ) ) > 0 and len (
self . user_manager . get_user ( user_b ) ) > 0
2019-03-14 00:01:02 +01:00
now = datetime . datetime . now ( )
2019-04-01 16:13:44 +02:00
match = Match ( n = settings . n , match_id = match_id , revoke_time = now + self . match_lifespan_timedelta ,
2019-04-04 13:44:48 +02:00
player_a_name = user_a , player_b_name = user_b , ranked = ranked )
query = " INSERT INTO matches (id, user_a, user_b, match_state, active_user, last_active, ranked) VALUES ( %s , %s , %s , %s , %s , %s , %s ) "
2019-03-27 17:23:04 +01:00
DatabaseConnection . global_single_execution (
2019-04-04 13:44:48 +02:00
query , ( match_id , user_a , user_b , match . to_json_state ( ) , match . get_current_player ( ) , get_sql_time ( now ) , ranked ) )
2019-03-14 00:01:02 +01:00
return match
2019-03-27 17:23:04 +01:00
2019-04-01 16:13:44 +02:00
def update_match ( self , match_id : str , match : Match , update_in_db = True ) - > None :
if ( update_in_db ) :
now = get_sql_time ( datetime . datetime . now ( ) )
query = " UPDATE matches SET match_state= %s , active_user= %s , last_active= %s WHERE id= %s "
DatabaseConnection . global_single_execution (
query , ( match . to_json_state ( ) , match . get_current_player ( ) , now , match_id ) )
2019-03-27 17:23:04 +01:00
2019-03-29 12:52:03 +01:00
# check whether we have to update the elo values (game over triggered by the last move)
2019-04-04 13:44:48 +02:00
if match . game_over and match . ranked :
2019-03-29 12:52:03 +01:00
if match . player_won is not None :
player_won = match . player_won
player_lost = match . player_a_name if player_won == match . player_b_name else match . player_b_name
elo_won = self . user_manager . get_elo ( player_won )
elo_lost = self . user_manager . get_elo ( player_lost )
# calculate elo values:
p_won = elo_p_win ( elo_won , elo_lost )
p_lost = 1 - p_won
new_elo_won = elo_update ( elo_won , 1 , p_won )
new_elo_lost = elo_update ( elo_lost , 0 , p_lost )
self . user_manager . update_elo ( player_won , new_elo_won )
self . user_manager . update_elo ( player_lost , new_elo_lost )
2019-04-01 16:13:44 +02:00
2019-03-29 12:52:03 +01:00
debug (
f " Match { match_id } is won by { player_won } over { player_lost } . Update elo-rankings: { elo_won } -> { new_elo_won } and { elo_lost } -> { new_elo_lost } " )
elif match . is_draw :
elo_a = self . user_manager . get_elo ( match . player_a_name )
elo_b = self . user_manager . get_elo ( match . player_b_name )
p_a_wins = elo_p_win ( elo_a , elo_b )
p_b_wins = 1 - p_a_wins
new_elo_a = elo_update ( elo_a , 0.5 , p_a_wins )
new_elo_b = elo_update ( elo_b , 0.5 , p_b_wins )
self . user_manager . update_elo ( match . player_a_name , new_elo_a )
self . user_manager . update_elo ( match . player_b_name , new_elo_b )
debug (
f " { match_id } between { match . player_a_name } and { match . player_b_name } ended in draw. Update elo-rankings: { elo_a } -> { new_elo_a } and { elo_b } -> { new_elo_b } " )
else :
# someone aborted a match. TODO: apply some penalty to the ranking
pass
def apply_move ( self , move_data : dict ) - > Match :
2019-03-21 12:06:22 +01:00
match = self . get_match ( move_data [ ' id ' ] )
if match is None :
return None
2019-03-27 17:23:04 +01:00
2019-03-21 12:06:22 +01:00
if not match . move ( move_data ) :
2019-03-27 17:23:04 +01:00
debug ( " error applying match move " )
2019-03-21 12:06:22 +01:00
return None
2019-03-27 17:23:04 +01:00
2019-03-21 12:06:22 +01:00
self . update_match ( move_data [ ' id ' ] , match )
2019-03-27 17:23:04 +01:00
debug ( " updated match " )
2019-03-21 12:06:22 +01:00
return match
2019-03-27 17:23:04 +01:00
2019-03-29 12:52:03 +01:00
def is_match ( self , player_a , player_b ) - > bool :
2019-03-27 17:23:04 +01:00
query = " SELECT * FROM matches WHERE (user_a= %s AND user_b= %s ) OR (user_a= %s AND user_b= %s ) "
2019-03-29 12:52:03 +01:00
return len ( DatabaseConnection . global_single_query ( query , ( player_a , player_b , player_b , player_a ) ) ) > 0
2019-03-14 00:01:02 +01:00
2019-03-29 12:52:03 +01:00
def delete_match ( self , match_id : str ) - > None :
2019-03-27 17:23:04 +01:00
query = " DELETE FROM matches WHERE id= %s "
DatabaseConnection . global_single_execution ( query , ( match_id ) )
2019-04-01 16:13:44 +02:00
def check_matches_lifespan ( self ) :
time_now = datetime . datetime . now ( )
time_revoke = time_now - self . match_lifespan_timedelta
query = " SELECT * FROM matches WHERE last_active < %s "
tmp = DatabaseConnection . global_single_query (
query , ( get_sql_time ( time_revoke ) ) )
for entry in tmp :
revoke_time = entry [ ' last_active ' ] + self . match_lifespan_timedelta
match = Match ( n = settings . n , match_id = entry [ ' id ' ] , revoke_time = revoke_time , player_a_name = entry [ ' user_a ' ] ,
player_b_name = entry [ ' user_b ' ] , json_state = entry [ ' match_state ' ] )
if ( not match . game_over ) :
2019-04-01 16:25:16 +02:00
2019-04-01 16:13:44 +02:00
match . game_over = True
2019-04-01 16:25:16 +02:00
if match . complete_field . __contains__ ( Match . FIELD_USER_A ) and match . complete_field . __contains__ ( Match . FIELD_USER_B ) :
# check who is the current player who did not make the move in time:
match . player_won = match . player_b_name if match . is_player_a else match . player_a_name
2019-04-01 16:13:44 +02:00
2019-04-01 16:25:16 +02:00
self . update_match ( entry [ ' id ' ] , match , False )
2019-04-01 16:13:44 +02:00
# delete matches from db:
debug ( " deleting revoked sessions: " + str ( tmp ) )
query = " DELETE FROM matches WHERE last_active < %s "
tmp = DatabaseConnection . global_single_execution (
query , ( get_sql_time ( time_revoke ) ) )