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
|
||||
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
2
deps/py-hanabi
vendored
|
@ -1 +1 @@
|
|||
Subproject commit 40baa59bd3b6ad622b39b975a363d65a5bfe39c0
|
||||
Subproject commit daea75053553fcc18cbc4dffaf00a7cf94fd32a1
|
|
@ -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()
|
||||
|
|
|
@ -6,3 +6,4 @@ python-dateutil
|
|||
unidecode
|
||||
requests
|
||||
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