def reader(self): try: while self.connected and self._reader_alive: if not self._should_read(): continue # read all that is there or wait for one byte self.reader_buffer_string += self.serial_instance\ .read(self.serial_instance.in_waiting or 1)\ .decode('UTF-8') if '\r\n' in self.reader_buffer_string: lines = self.reader_buffer_string.split('\r\n') self.reader_last_received = lines[-2] self.reader_buffer_string = lines[-1] self.reader_last_read_time = utc_now() if self.reader_last_received[:1] == ID_TYPE_EM4200: for reader_listener in self.reader_listeners: reader_listener.handle_card_read( EM4200Card(self.reader_last_received[1:])) else: for reader_listener in self.reader_listeners: reader_listener.handle_data( self.reader_last_received) except serial.SerialException: self.connected = False # TODO: Handle exception (reconnect?) instead of re-raise raise
def _check_pending_card_registration(self, card): pending_registration = self.firebase.database()\ .child('pending_registrations')\ .child(card.get_uid()) \ .get().val() if pending_registration is None: return False registration_datetime = datetime.fromtimestamp( int(pending_registration['timestamp'] / 1000)).replace(tzinfo=pytz.utc) if (utc_now() - registration_datetime ).total_seconds() > GAME_CARD_REGISTRATION_TIMEOUT: # Send a notification to listeners for game_listener in self.game_listeners: game_listener.on_pending_card_registration_timeout( pending_registration, card) return False self.register_new_game_card(card, pending_registration['user_id']) self.firebase.database() \ .child('pending_registrations') \ .child(card.get_uid()) \ .remove() return True
def __init__(self, serial_port, baudrate=9600, read_delay=1): self.serial_port = serial_port self.baudrate = baudrate self.read_delay = read_delay self.connected = False self._reader_alive = None self.receiver_thread = None self.reader_listeners = [] self.reader_buffer_string = '' self.reader_last_received = None self.reader_last_read_time = utc_now() try: self.serial_instance = serial.serial_for_url( self.serial_port.path, self.baudrate, do_not_open=True ) if not hasattr(self.serial_instance, 'cancel_read'): # enable timeout for alive flag polling if cancel_read is not available self.serial_instance.timeout = 1 except serial.SerialException as exception: raise ReaderCouldNotConnect( self.serial_port, exception )
def should_reset(self): if len(self.players) == 0 or self.has_all_needed_players(): return False for player in self.players: if (utc_now() - self.get_player_join_time(player) ).total_seconds() > GAME_PLAYER_REGISTRATION_TIMEOUT: return True return False
def __init__(self, hid_ports, read_delay=1): self.hid_ports = hid_ports self.reader_devices = [] self.selector = selectors.DefaultSelector() self.read_delay = read_delay self.connected = False self._reader_alive = None self.receiver_thread = None self.reader_listeners = [] self.reader_buffer_strings = [] self.reader_last_received = [] self.reader_last_read_time = [] for i in range(len(hid_ports)): self.reader_buffer_strings.append('') self.reader_last_received.append(None) self.reader_last_read_time.append(utc_now())
def _should_read(self, reader_index): return (utc_now() - self.reader_last_read_time[reader_index] ).total_seconds() > self.read_delay
def _should_read(self): return (utc_now() - self.reader_last_read_time).total_seconds() > self.read_delay
def should_sync_slack_information(self): return (utc_now() - self.get_slack_last_sync() ).total_seconds() > SLACK_SYNC_INTERVAL
def update_slack_last_sync(self): self.set_slack_last_sync(utc_now())
def end_session(self, winner_player): end_time = utc_now() all_players = self.get_current_session().get_players().copy() # Remove the winner from the player list and return the first player (loser for now) self.get_current_session().remove_player(winner_player) loser_player = self.get_current_session().get_player(0) # Calculate the players new rating winner_elo_rating = calculate_new_rating( player_rating=winner_player.get_elo_rating(), opponent_rating=loser_player.get_elo_rating(), player_won=True) loser_elo_rating = calculate_new_rating( player_rating=loser_player.get_elo_rating(), opponent_rating=winner_player.get_elo_rating(), player_won=False) winner_trueskill_rating, loser_trueskill_rating = rate_1vs1( winner_player.get_trueskill_rating(), loser_player.get_trueskill_rating()) # Push the current session (which has ended) to the list of sessions in Firebase results = self.get_db().child('sessions').push({ 'session_started': self.get_current_session().start_time.isoformat(), 'session_ended': end_time.isoformat(), 'session_seconds': (end_time - self.get_current_session().start_time).total_seconds(), 'trueskill_quality': quality_1vs1(winner_player.get_trueskill_rating(), loser_player.get_trueskill_rating()), 'winner': { 'slack_user_id': winner_player.get_slack_user_id(), 'trueskill_rating': { 'mu': { 'before': winner_player.get_trueskill_rating().mu, 'after': winner_trueskill_rating.mu, 'delta': winner_trueskill_rating.mu - winner_player.get_trueskill_rating().mu }, 'sigma': { 'before': winner_player.get_trueskill_rating().sigma, 'after': winner_trueskill_rating.sigma, 'delta': winner_trueskill_rating.sigma - winner_player.get_trueskill_rating().sigma } }, 'elo_rating': { 'before': winner_player.get_elo_rating(), 'after': winner_elo_rating, 'delta': winner_elo_rating - winner_player.get_elo_rating() } }, 'loser': { 'slack_user_id': loser_player.get_slack_user_id(), 'trueskill_rating': { 'mu': { 'before': loser_player.get_trueskill_rating().mu, 'after': loser_trueskill_rating.mu, 'delta': loser_trueskill_rating.mu - loser_player.get_trueskill_rating().mu }, 'sigma': { 'before': loser_player.get_trueskill_rating().sigma, 'after': loser_trueskill_rating.sigma, 'delta': loser_trueskill_rating.sigma - loser_player.get_trueskill_rating().sigma } }, 'elo_rating': { 'before': loser_player.get_elo_rating(), 'after': loser_elo_rating, 'delta': loser_elo_rating - loser_player.get_elo_rating() } } }) # Push the current session id/pk to the list of sessions for each card for player in all_players: if player.get_card().get_uid() == winner_player.get_card().get_uid( ): is_winner = True new_elo_rating = winner_elo_rating new_trueskill_rating = winner_trueskill_rating else: is_winner = False new_elo_rating = loser_elo_rating new_trueskill_rating = loser_trueskill_rating self.firebase.database()\ .child('players')\ .child(player.get_slack_user_id())\ .child('sessions')\ .child(self.game_slug)\ .child(results['name'])\ .set({ 'card_uid': player.get_card().get_uid(), 'winner': is_winner, 'elo_rating': { 'new': new_elo_rating, 'delta': new_elo_rating - player.get_elo_rating() }, 'trueskill_rating': { 'mu': { 'new': new_trueskill_rating.mu, 'delta': new_trueskill_rating.mu - player.get_trueskill_rating().mu }, 'sigma': { 'new': new_trueskill_rating.sigma, 'delta': new_trueskill_rating.sigma - player.get_trueskill_rating().sigma } } }) existing_player_statistics = self.get_db()\ .child('player_statistics')\ .child(player.get_slack_user_id())\ .get().val() # Set the player statistics self.get_db().child('player_statistics').child( player.get_slack_user_id()).set({ 'elo_rating': new_elo_rating, 'trueskill_rating': { 'mu': new_trueskill_rating.mu, 'sigma': new_trueskill_rating.sigma }, 'total_games': existing_player_statistics['total_games'] + 1, 'games_won': existing_player_statistics['games_won'] + (1 if is_winner else 0), 'games_lost': existing_player_statistics['games_lost'] + (1 if not is_winner else 0), 'seconds_played': existing_player_statistics['seconds_played'] + (end_time - self.get_current_session().start_time).total_seconds() }) # Send a notification to listeners for game_listener in self.game_listeners: game_listener.on_end_session( winner_player=winner_player, winner_new_elo_rating=winner_elo_rating, winner_new_trueskill_rating=winner_trueskill_rating, loser_player=loser_player, loser_new_elo_rating=loser_elo_rating, loser_new_trueskill_rating=loser_trueskill_rating) # Create a new session placeholder (it doesn't start until we call .start()) self.current_session = GameSession(self.min_max_card_count) # Reset / remove the remote session self.get_db().child('current_session').remove()
def register_new_game_card(self, card, slack_user_id): existing_card = self.firebase.database().child('cards').child( card.get_uid()).get().val() if existing_card is None: # Card does not exist, insert the card self.firebase.database().child('cards').child(card.get_uid()).set({ 'slack_user_id': slack_user_id, 'registration_date': utc_now().isoformat() }) else: # Card exists in the database if 'slack_user_id' not in existing_card: # Card exists, but is not registered to a player, update the owner (player) self.firebase.database().child('cards').child( card.get_uid()).update({'slack_user_id': slack_user_id}) elif existing_card['slack_user_id'] == slack_user_id: # The card is already registered to this Slack user, ignore logger.debug( f'Card {card} is already registered under user ID {slack_user_id}, doing nothing' ) return else: # The card exists under another player raise CardExists(card, existing_card['slack_user_id']) # Does the player exists in the database? existing_player = self.firebase.database().child('players').child( slack_user_id).get().val() if existing_player is not None: # Player exists in the database, just append the card to the player self.firebase.database()\ .child('players')\ .child(slack_user_id)\ .child('cards')\ .child(card.get_uid())\ .set(True) player = GamePlayer( card=card, slack_user_id=slack_user_id, slack_username=existing_player['slack_username'], slack_first_name=existing_player['slack_first_name'], slack_avatar_url=existing_player['slack_avatar_url']) else: # Player does not exist, get the slack information of the user slack_information = self._get_slack_information(slack_user_id) new_player = { **slack_information, **{ 'slack_last_sync': utc_now().isoformat(), 'registration_date': utc_now().isoformat(), 'cards': { card.get_uid(): True } } } player = GamePlayer( card=card, slack_user_id=slack_user_id, slack_username=new_player['slack_username'], slack_first_name=new_player['slack_first_name'], slack_avatar_url=new_player['slack_avatar_url'], slack_last_sync=new_player['slack_last_sync']) # Add the new player to the database, with the card added in the card list self.firebase.database().child('players').child(slack_user_id).set( new_player) # Add the player to the player statistics of the current game_slug self.get_db().child('player_statistics').child(slack_user_id).set({ 'trueskill_rating': { 'mu': Rating().mu, 'sigma': Rating().sigma, }, 'elo_rating': 1200, 'total_games': 0, 'games_won': 0, 'games_lost': 0, 'seconds_played': 0 }) # Send a notification to listeners for game_listener in self.game_listeners: game_listener.on_new_card_registration(player, card)
def start(self): # TODO: beep 3 times with buzzer self.start_time = utc_now()
def get_seconds_elapsed(self): return (utc_now() - self.start_time).total_seconds()
def add_player(self, player): self.players.append(player) self.join_times[player.get_slack_user_id()] = utc_now()