Fetch details of game: Added player seats

This commit is contained in:
Maximilian Keßler 2023-11-23 12:52:56 +01:00
parent 7426f8018a
commit 56524cd28e
Signed by: max
GPG Key ID: BCC5A619923C0BA5
5 changed files with 151 additions and 26 deletions

View File

@ -1,5 +1,5 @@
import json
from typing import Optional
from typing import Optional, List, Dict
import psycopg2
import psycopg2.extensions
@ -9,6 +9,7 @@ import requests
import unidecode
import constants
import utils
from config import config_manager
from log_setup import logger
@ -123,17 +124,44 @@ def initialize_variant_base_ratings():
conn.commit()
def normalize_username(username: str) -> str:
decoded = unidecode.unidecode(username)
return decoded.lower()
def get_user_id(player_name: str) -> Optional[id]:
def get_user_id(player_name: str) -> Optional[int]:
cur = conn_manager.get_new_cursor()
cur.execute("SELECT id FROM users WHERE player_name = (%s)", (player_name,))
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):
conn = conn_manager.get_connection()
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):
normalized_username = normalize_username(hanabi_username)
normalized_username = utils.normalize_username(hanabi_username)
user_id = get_user_id(player_name)
if user_id is None:
logger.error("Player {} not found in database, cannot add username to it.".format(player_name))

2
deps/py-hanabi vendored

@ -1 +1 @@
Subproject commit 40baa59bd3b6ad622b39b975a363d65a5bfe39c0
Subproject commit daea75053553fcc18cbc4dffaf00a7cf94fd32a1

View File

@ -5,8 +5,12 @@ import platformdirs
import requests_cache
import psycopg2.extras
import hanabi.live.hanab_live
import hanabi.hanab_game
import constants
import database
import utils
from database import conn_manager
from log_setup import logger
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]:
logger.debug("Processing entry {}".format(game_json))
config = config_manager.get_config()
# Check if the game is one that we accept
# Parse game properties
game_id = game_json["id"]
players = game_json["playerNames"]
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"]
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
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))
# Now, check if the game is one that we accept for league
if not utils.are_game_options_allowed(game_id, game_options):
return
# Check if player count matches
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))
if not utils.is_player_count_allowed(game_id, num_players):
return
# Check if the variant was ok
if var_id not in variant_ids:
logger.debug("Rejected game {} due to invalid variant id {}".format(game_id, var_id))
return
@ -188,10 +189,69 @@ def detailed_fetch_game(game_id: int):
logger.error(err_msg)
raise ConnectionError(err_msg)
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.execute("SELECT user_accounts.normalized_username, user_accounts.user_id FROM user_accounts")
user_name_dict: Dict[str, int] = {}
for username, user_id in cur.fetchall():
user_name_dict[username] = user_id
info = process_game_entry(game_json, user_name_dict, database.get_variant_ids())
print(info)
# Insert the game into the database if not present
cur.execute("INSERT INTO games "
"(id, num_players, variant_id, seed, score, num_turns) "
"VALUES "
"(%s, %s, %s, %s, %s, %s) "
"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()

View File

@ -6,3 +6,4 @@ python-dateutil
unidecode
requests
requests_cache
termcolor

36
utils.py Normal file
View 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