forked from Hanabi/hanabi-league
Fetch details of game: Added player seats
This commit is contained in:
parent
7426f8018a
commit
56524cd28e
5 changed files with 151 additions and 26 deletions
44
database.py
44
database.py
|
@ -1,5 +1,5 @@
|
||||||
import json
|
import json
|
||||||
from typing import Optional
|
from typing import Optional, List, Dict
|
||||||
|
|
||||||
import psycopg2
|
import psycopg2
|
||||||
import psycopg2.extensions
|
import psycopg2.extensions
|
||||||
|
@ -9,6 +9,7 @@ import requests
|
||||||
import unidecode
|
import unidecode
|
||||||
|
|
||||||
import constants
|
import constants
|
||||||
|
import utils
|
||||||
from config import config_manager
|
from config import config_manager
|
||||||
from log_setup import logger
|
from log_setup import logger
|
||||||
|
|
||||||
|
@ -123,17 +124,44 @@ def initialize_variant_base_ratings():
|
||||||
conn.commit()
|
conn.commit()
|
||||||
|
|
||||||
|
|
||||||
def normalize_username(username: str) -> str:
|
def get_user_id(player_name: str) -> Optional[int]:
|
||||||
decoded = unidecode.unidecode(username)
|
|
||||||
return decoded.lower()
|
|
||||||
|
|
||||||
|
|
||||||
def get_user_id(player_name: str) -> Optional[id]:
|
|
||||||
cur = conn_manager.get_new_cursor()
|
cur = conn_manager.get_new_cursor()
|
||||||
cur.execute("SELECT id FROM users WHERE player_name = (%s)", (player_name,))
|
cur.execute("SELECT id FROM users WHERE player_name = (%s)", (player_name,))
|
||||||
return cur.fetchone()
|
return cur.fetchone()
|
||||||
|
|
||||||
|
|
||||||
|
def get_user_ids_from_normalized_usernames(normalized_usernames: List[str]) -> List[int] | str:
|
||||||
|
"""
|
||||||
|
@rtype: If all users are registered, list of their ids in the same order.
|
||||||
|
Otherwise, name of a user that is not registered.
|
||||||
|
@warning If usernames are not present in the database, there is no corresponding key in the returned dictionary
|
||||||
|
"""
|
||||||
|
cur = conn_manager.get_new_cursor()
|
||||||
|
cur.execute("SELECT normalized_username, user_id "
|
||||||
|
"FROM user_accounts "
|
||||||
|
"WHERE normalized_username IN ({})".format(",".join("%s" for _ in normalized_usernames)),
|
||||||
|
normalized_usernames
|
||||||
|
)
|
||||||
|
# Build up dict from the specified user ids
|
||||||
|
user_dict: Dict[str, int] = {}
|
||||||
|
for normalized_username, user_id in cur.fetchall():
|
||||||
|
user_dict[normalized_username] = user_id
|
||||||
|
|
||||||
|
user_ids = []
|
||||||
|
for normalized_username in normalized_usernames:
|
||||||
|
if normalized_username not in user_dict.keys():
|
||||||
|
return normalized_username
|
||||||
|
else:
|
||||||
|
user_ids.append(user_dict[normalized_username])
|
||||||
|
return user_ids
|
||||||
|
|
||||||
|
|
||||||
|
def get_variant_id(variant_name: str) -> Optional[int]:
|
||||||
|
cur = conn_manager.get_new_cursor()
|
||||||
|
cur.execute("SELECT id FROM variants WHERE name = %s", (variant_name,))
|
||||||
|
return cur.fetchone()
|
||||||
|
|
||||||
|
|
||||||
def add_player_name(player_name: str):
|
def add_player_name(player_name: str):
|
||||||
conn = conn_manager.get_connection()
|
conn = conn_manager.get_connection()
|
||||||
cur = conn.cursor()
|
cur = conn.cursor()
|
||||||
|
@ -146,7 +174,7 @@ def add_player_name(player_name: str):
|
||||||
|
|
||||||
|
|
||||||
def add_user_name_to_player(hanabi_username: str, player_name: str):
|
def add_user_name_to_player(hanabi_username: str, player_name: str):
|
||||||
normalized_username = normalize_username(hanabi_username)
|
normalized_username = utils.normalize_username(hanabi_username)
|
||||||
user_id = get_user_id(player_name)
|
user_id = get_user_id(player_name)
|
||||||
if user_id is None:
|
if user_id is None:
|
||||||
logger.error("Player {} not found in database, cannot add username to it.".format(player_name))
|
logger.error("Player {} not found in database, cannot add username to it.".format(player_name))
|
||||||
|
|
2
deps/py-hanabi
vendored
2
deps/py-hanabi
vendored
|
@ -1 +1 @@
|
||||||
Subproject commit 40baa59bd3b6ad622b39b975a363d65a5bfe39c0
|
Subproject commit daea75053553fcc18cbc4dffaf00a7cf94fd32a1
|
|
@ -5,8 +5,12 @@ import platformdirs
|
||||||
import requests_cache
|
import requests_cache
|
||||||
import psycopg2.extras
|
import psycopg2.extras
|
||||||
|
|
||||||
|
import hanabi.live.hanab_live
|
||||||
|
import hanabi.hanab_game
|
||||||
|
|
||||||
import constants
|
import constants
|
||||||
import database
|
import database
|
||||||
|
import utils
|
||||||
from database import conn_manager
|
from database import conn_manager
|
||||||
from log_setup import logger
|
from log_setup import logger
|
||||||
from config import config_manager
|
from config import config_manager
|
||||||
|
@ -48,7 +52,8 @@ def fetch_games_for_player(username: str, latest_game_id: int):
|
||||||
def process_game_entry(game_json: Dict, username_dict: Dict, variant_ids: List[int]) -> Optional[GameInfo]:
|
def process_game_entry(game_json: Dict, username_dict: Dict, variant_ids: List[int]) -> Optional[GameInfo]:
|
||||||
logger.debug("Processing entry {}".format(game_json))
|
logger.debug("Processing entry {}".format(game_json))
|
||||||
config = config_manager.get_config()
|
config = config_manager.get_config()
|
||||||
# Check if the game is one that we accept
|
|
||||||
|
# Parse game properties
|
||||||
game_id = game_json["id"]
|
game_id = game_json["id"]
|
||||||
players = game_json["playerNames"]
|
players = game_json["playerNames"]
|
||||||
num_players = len(players)
|
num_players = len(players)
|
||||||
|
@ -59,20 +64,16 @@ def process_game_entry(game_json: Dict, username_dict: Dict, variant_ids: List[i
|
||||||
game_options = game_json["options"]
|
game_options = game_json["options"]
|
||||||
var_id = game_options["variantID"]
|
var_id = game_options["variantID"]
|
||||||
|
|
||||||
normalized_usernames = [database.normalize_username(username) for username in players]
|
normalized_usernames = [utils.normalize_username(username) for username in players]
|
||||||
|
|
||||||
# Check that the game has no special options enabled
|
# Now, check if the game is one that we accept for league
|
||||||
for forbidden_option in constants.FORBIDDEN_GAME_OPTIONS:
|
|
||||||
if game_options.get(forbidden_option, False):
|
if not utils.are_game_options_allowed(game_id, game_options):
|
||||||
logger.debug("Rejected game {} due to option {} set".format(game_id, forbidden_option))
|
|
||||||
return
|
return
|
||||||
|
|
||||||
# Check if player count matches
|
if not utils.is_player_count_allowed(game_id, num_players):
|
||||||
if not (config.min_player_count <= num_players <= config.max_player_count):
|
|
||||||
logger.debug("Rejected game {} due to invalid number of players ({})".format(game_id, num_players))
|
|
||||||
return
|
return
|
||||||
|
|
||||||
# Check if the variant was ok
|
|
||||||
if var_id not in variant_ids:
|
if var_id not in variant_ids:
|
||||||
logger.debug("Rejected game {} due to invalid variant id {}".format(game_id, var_id))
|
logger.debug("Rejected game {} due to invalid variant id {}".format(game_id, var_id))
|
||||||
return
|
return
|
||||||
|
@ -188,10 +189,69 @@ def detailed_fetch_game(game_id: int):
|
||||||
logger.error(err_msg)
|
logger.error(err_msg)
|
||||||
raise ConnectionError(err_msg)
|
raise ConnectionError(err_msg)
|
||||||
game_json = json.loads(response.text)
|
game_json = json.loads(response.text)
|
||||||
|
|
||||||
|
instance, actions = hanabi.live.hanab_live.parse_json_game(game_json, False)
|
||||||
|
print(instance, actions, instance.deck)
|
||||||
|
print(game_json)
|
||||||
|
|
||||||
|
game_id = game_json["id"]
|
||||||
|
players = game_json["players"]
|
||||||
|
num_players = len(players)
|
||||||
|
seed = game_json["seed"]
|
||||||
|
game_options = game_json.get("options", {})
|
||||||
|
var_name = game_options.get("variant", "No Variant")
|
||||||
|
|
||||||
|
if not utils.are_game_options_allowed(game_id, game_options):
|
||||||
|
return
|
||||||
|
|
||||||
|
if not utils.is_player_count_allowed(game_id, num_players):
|
||||||
|
return
|
||||||
|
|
||||||
|
var_id = database.get_variant_id(var_name)
|
||||||
|
if var_id is None:
|
||||||
|
logger.debug("Rejected game {} due to invalid variant id {}".format(game_id, var_id))
|
||||||
|
return
|
||||||
|
|
||||||
|
# All game options are ok, now check if the number of players is okay.
|
||||||
|
normalized_usernames = [utils.normalize_username(username) for username in players]
|
||||||
|
user_ids = database.get_user_ids_from_normalized_usernames(normalized_usernames)
|
||||||
|
# The return value here is a str if there was an unregistered participant
|
||||||
|
if type(user_ids) is str:
|
||||||
|
logger.debug("Rejected game {} due to unregistered participant {}".format(game_id, user_ids))
|
||||||
|
return
|
||||||
|
|
||||||
|
# Now, we can start to actually process the game details
|
||||||
|
instance, actions = hanabi.live.hanab_live.parse_json_game(game_json, False)
|
||||||
|
game = hanabi.hanab_game.GameState(instance)
|
||||||
|
for action in actions:
|
||||||
|
game.make_action(action)
|
||||||
|
|
||||||
|
conn = conn_manager.get_connection()
|
||||||
cur = conn_manager.get_new_cursor()
|
cur = conn_manager.get_new_cursor()
|
||||||
cur.execute("SELECT user_accounts.normalized_username, user_accounts.user_id FROM user_accounts")
|
# Insert the game into the database if not present
|
||||||
user_name_dict: Dict[str, int] = {}
|
cur.execute("INSERT INTO games "
|
||||||
for username, user_id in cur.fetchall():
|
"(id, num_players, variant_id, seed, score, num_turns) "
|
||||||
user_name_dict[username] = user_id
|
"VALUES "
|
||||||
info = process_game_entry(game_json, user_name_dict, database.get_variant_ids())
|
"(%s, %s, %s, %s, %s, %s) "
|
||||||
print(info)
|
"ON CONFLICT (id) DO NOTHING",
|
||||||
|
(game_id, num_players, var_id, seed, game.score, len(actions)))
|
||||||
|
|
||||||
|
game_participants_vals = []
|
||||||
|
for seat, user_id in enumerate(user_ids):
|
||||||
|
tup = (game_id, user_id, seat)
|
||||||
|
game_participants_vals.append(tup)
|
||||||
|
|
||||||
|
# This inserts the game participants now.
|
||||||
|
# Note that the participants might already be stored, but not their seat (since we do not know the seat when only
|
||||||
|
# getting a game list from the server on fetching the games played by some player)
|
||||||
|
psycopg2.extras.execute_values(
|
||||||
|
cur,
|
||||||
|
"INSERT INTO game_participants "
|
||||||
|
"(game_id, user_id, seat) "
|
||||||
|
"VALUES %s "
|
||||||
|
"ON CONFLICT (game_id, user_id) DO UPDATE "
|
||||||
|
"SET seat = EXCLUDED.seat",
|
||||||
|
game_participants_vals
|
||||||
|
)
|
||||||
|
# DB is now in a consistent state again: We made sure the game and its participants are added.
|
||||||
|
conn.commit()
|
||||||
|
|
|
@ -6,3 +6,4 @@ python-dateutil
|
||||||
unidecode
|
unidecode
|
||||||
requests
|
requests
|
||||||
requests_cache
|
requests_cache
|
||||||
|
termcolor
|
||||||
|
|
36
utils.py
Normal file
36
utils.py
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
from typing import Dict
|
||||||
|
|
||||||
|
import unidecode
|
||||||
|
|
||||||
|
import constants
|
||||||
|
from config import config_manager
|
||||||
|
|
||||||
|
from log_setup import logger
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_username(username: str) -> str:
|
||||||
|
decoded = unidecode.unidecode(username)
|
||||||
|
return decoded.lower()
|
||||||
|
|
||||||
|
|
||||||
|
def are_game_options_allowed(game_id: int, game_options: Dict) -> bool:
|
||||||
|
"""
|
||||||
|
Check if the game options are allowed for league.
|
||||||
|
"""
|
||||||
|
for forbidden_option in constants.FORBIDDEN_GAME_OPTIONS:
|
||||||
|
if game_options.get(forbidden_option, False):
|
||||||
|
logger.debug("Rejected game {} due to option {} set".format(game_id, forbidden_option))
|
||||||
|
return False
|
||||||
|
# All options ok
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def is_player_count_allowed(game_id: int, num_players: int) -> bool:
|
||||||
|
"""
|
||||||
|
Check if the player count is allowed for league.
|
||||||
|
"""
|
||||||
|
config = config_manager.get_config()
|
||||||
|
if not (config.min_player_count <= num_players <= config.max_player_count):
|
||||||
|
logger.debug("Rejected game {} due to invalid number of players ({})".format(game_id, num_players))
|
||||||
|
return False
|
||||||
|
return True
|
Loading…
Reference in a new issue