Example #1
0
class RaceRoom(command.Module):

    def get_new_raceinfo():
        to_return = RaceInfo()
        to_return.seed_fixed = False
        to_return.seeded = True
        to_return.character = 'Cadence'
        to_return.descriptor = 'Condor Race'
        to_return.sudden_death = False
        to_return.flagplant = False
        to_return.seed = seedgen.get_new_seed()
        return to_return

    def __init__(self, condor_module, condor_match, race_channel):
        self.channel = race_channel                                 #The channel in which this race is taking place
        self.is_closed = False                                      #True if room has been closed
        self.match = condor_match

        self.events = Events()

        self.race = None                                            #The current race
        self.recorded_race = False                                  #Whether the current race has been recorded

        self.entered_racers = []                                    #Racers that have typed .here in this channel
        self.before_races = True
        self.cancelling_racers = []                                 #Racers that have typed .cancel

        self._cm = condor_module           

        self.command_types = [command.DefaultHelp(self),
                              Here(self),
                              Ready(self),
                              Unready(self),
                              Done(self),
                              Undone(self),
                              Cancel(self),
                              #Forfeit(self),
                              #Unforfeit(self),
                              #Comment(self),
                              #Igt(self),
                              Time(self),
                              Contest(self),
                              ForceCancel(self),
                              ForceChangeWinner(self),
                              #ForceClose(self),
                              ForceForfeit(self),
                              #ForceForfeitAll(self),
                              ForceRecordRace(self),
                              ForceNewRace(self),
                              ForceCancelRace(self),
                              ForceRecordMatch(self),
                              #Kick(self),
                              ]

    @property
    def infostr(self):
        return 'Race'

    @property
    def client(self):
        return self._cm.necrobot.client

    @property
    def necrobot(self):
        return self._cm.necrobot

    @property
    def condordb(self):
        return self._cm.condordb

    #True if the user has admin permissions for this race
    def is_race_admin(self, member):
        admin_roles = self._cm.necrobot.admin_roles
        for role in member.roles:
            if role in admin_roles:
                return True
        
        return False

    # Set up the leaderboard etc. Should be called after creation; code not put into __init__ b/c coroutine
    @asyncio.coroutine
    def initialize(self, users_to_mention=[]):
        yield from self.update_leaderboard()
        asyncio.ensure_future(self.countdown_to_match_start())

    # Write text to the raceroom. Return a Message for the text written
    @asyncio.coroutine
    def write(self, text):
        return self.client.send_message(self.channel, text)

    # Write text to the bot_notifications channel.
    @asyncio.coroutine
    def alert_staff(self, text):
        return self.client.send_message(self._cm.necrobot.notifications_channel, text)

    # Register a racer as wanting to cancel
    @asyncio.coroutine
    def wants_to_cancel(self, member):
        for racer in self.match.racers:
            if int(racer.discord_id) == int(member.id) and not racer in self.cancelling_racers:
                self.cancelling_racers.append(racer)

        if len(self.cancelling_racers) == 2:
            yield from self.cancel_race()
            return True
        else:
            return False

    # Cancel the race
    @asyncio.coroutine
    def cancel_race(self):
        if self.race and not self.race.is_before_race:
            self.cancelling_racers = []
            yield from self.race.cancel()
            yield from self.record_race(cancelled=True)
            yield from self.write('The current race was cancelled.')
        else:
            self.cancelling_racers = []
            race_number = int(self._cm.condordb.largest_recorded_race_number(self.match))
            if race_number > 0:
                self.condordb.cancel_race(self.match, race_number)
                yield from self.write('The previous race was cancelled.'.format(race_number))
                yield from self.update_leaderboard()                  
                

    #Updates the leaderboard
    @asyncio.coroutine
    def update_leaderboard(self):
        if self.race or self.match.time_until_match.total_seconds() < 0:
            topic = '``` \n'
            topic += 'Necrodancer World Cup Match (Cadence Seeded)\n'
            max_name_len = 0
            for racer in self.match.racers:
                max_name_len = max(max_name_len, len(racer.discord_name))
            for racer in self.match.racers:
                wins = self._cm.condordb.number_of_wins(self.match, racer, count_draws=True)
                topic += '     ' + racer.discord_name + (' ' * (max_name_len - len(racer.discord_name))) + ' --- Wins: {0}\n'.format(str(round(wins,1) if wins % 1 else int(wins)))

            race_number = self._cm.condordb.number_of_finished_races(self.match) + 1
            if race_number > config.RACE_NUMBER_OF_RACES:
                topic += 'Match complete. \n'
            else:
                topic += 'Current race: #{}\n'.format(race_number)
                if self.race:
                    topic += self.race.leaderboard

            topic += '\n ```'
            asyncio.ensure_future(self.client.edit_channel(self.channel, topic=topic))
        else:
            topic_str = '``` \n'
            minutes_until_match = int( (self.match.time_until_match.total_seconds() + 30) // 60 )
            topic_str += 'The race is scheduled to begin in {0} minutes! Please let the bot know you\'re here by typing .here. \n\n'.format(minutes_until_match)

            waiting_str = ''
            if self.match.racer_1 not in self.entered_racers:
                waiting_str += self.match.racer_1.twitch_name + ', '
            if self.match.racer_2 not in self.entered_racers:
                waiting_str += self.match.racer_2.twitch_name + ', '

            topic_str += 'Still waiting for .here from: {0} \n'.format(waiting_str[:-2]) if waiting_str else 'Both racers are here!\n'

            topic_str += '```'
                
            asyncio.ensure_future(self.client.edit_channel(self.channel, topic=topic_str))

    @asyncio.coroutine
    def alert_racers(self, send_pm=False):
        member_1 = self._cm.necrobot.find_member_with_id(self.match.racer_1.discord_id)
        member_2 = self._cm.necrobot.find_member_with_id(self.match.racer_2.discord_id)

        alert_str = ''
        if member_1:
            alert_str += member_1.mention + ', '
        if member_2:
            alert_str += member_2.mention + ', '

        minutes_until_match = int( (self.match.time_until_match.total_seconds() + 30) // 60 )
        if alert_str:
            yield from self.write('{0}: The match is scheduled to begin in {1} minutes.'.format(alert_str[:-2], minutes_until_match))

        if send_pm:
            if member_1:
                yield from self.client.send_message(member_1, '{0}: Your match with {1} is scheduled to begin in {2} minutes.'.format(member_1.mention, self.match.racer_2.escaped_twitch_name, minutes_until_match))
            if member_2:
                yield from self.client.send_message(member_2, '{0}: Your match with {1} is scheduled to begin in {2} minutes.'.format(member_2.mention, self.match.racer_1.escaped_twitch_name, minutes_until_match))

    @asyncio.coroutine
    def countdown_to_match_start(self):        
        time_until_match = self.match.time_until_match
        asyncio.ensure_future(self.constantly_update_leaderboard())

        if time_until_match < datetime.timedelta(seconds=0):
            if not self.played_all_races:
                yield from self.write('I believe that I was just restarted; an error may have occurred. I am beginning a new race and attempting to pick up this ' \
                                      'match where we left off. If this is an error, or if there are unrecorded races, please contact CoNDOR Staff (`.staff`).')
                yield from self.begin_new_race()
        else:
            pm_warning = datetime.timedelta(minutes=30)
            first_warning = datetime.timedelta(minutes=15)
            alert_staff_warning = datetime.timedelta(minutes=5)

            if time_until_match > pm_warning:
                yield from asyncio.sleep( (time_until_match - pm_warning).total_seconds() )
                if not self.race:
                    yield from self.alert_racers(send_pm=True)                

            time_until_match = self.match.time_until_match            
            if time_until_match > first_warning:
                yield from asyncio.sleep( (time_until_match - first_warning).total_seconds() )
                if not self.race:
                    yield from self.alert_racers()

            time_until_match = self.match.time_until_match
            if time_until_match > alert_staff_warning:
                yield from asyncio.sleep( (time_until_match - alert_staff_warning).total_seconds() )

            # this is done at the alert_staff_warning, unless this function was called after the alert_staff_warning, in which case do it immediately
            if not self.race:
                yield from self.alert_racers()            
                for racer in self.match.racers:
                    if racer not in self.entered_racers:
                        discord_name = ''
                        if racer.discord_name:
                            discord_name = ' (Discord name: {0})'.format(racer.discord_name)
                        minutes_until_race = int( (self.match.time_until_match.total_seconds() + 30) // 60)
                        yield from self.alert_staff('Alert: {0}{1} has not yet shown up for their match, which is scheduled in {2} minutes.'.format(racer.escaped_twitch_name, discord_name, minutes_until_race))

                yield from self._cm.post_match_alert(self.match)

            yield from asyncio.sleep(self.match.time_until_match.total_seconds())

            if not self.race:
                yield from self.begin_new_race()

    @asyncio.coroutine
    def constantly_update_leaderboard(self):
        while self.match.time_until_match.total_seconds() > 0:
            asyncio.ensure_future(self.update_leaderboard())
            yield from asyncio.sleep(30)

    @asyncio.coroutine
    def begin_new_race(self):
        self.cancelling_racers = []
        self.before_races = False
        self.race = Race(self, RaceRoom.get_new_raceinfo(), self._cm.condordb)
        yield from self.race.initialize()
        self.recorded_race = False
        
        for racer in self.match.racers:
            racer_as_member = self._cm.necrobot.find_member_with_id(racer.discord_id)
            if racer_as_member:
                yield from self.race.enter_racer(racer_as_member)
            else:
                yield from self.write('Error: Couldn\'t find the racer {0}. Please contact CoNDOR Staff (`.staff`).'.format(racer.escaped_twitch_name))
                
        yield from self.update_leaderboard()

        race_number = int(self._cm.condordb.number_of_finished_races(self.match) + 1)
        race_str = '{}th'.format(race_number)
        if race_number == int(1):
            race_str = 'first'
        elif race_number == int(2):
            race_str = 'second'
        elif race_number == int(3):
            race_str = 'third'
            
        yield from self.write('Please input the seed ({1}) and type `.ready` when you are ready for the {0} race. '\
                              'When both racers `.ready`, the race will begin.'.format(race_str, self.race.race_info.seed))

    # Returns true if all racers are ready
    @property
    def all_racers_ready(self):
        return self.race and self.race.num_not_ready == 0

    @property
    def played_all_races(self):
        if self.match.is_best_of:
            return self._cm.condordb.number_of_wins_of_leader(self.match) >= (self.match.number_of_races//2 + 1)
        else:
            return self._cm.condordb.number_of_finished_races(self.match) >= self.match.number_of_races

    @property
    def race_to_contest(self):
        if not self.race:
            return 0

        if self.race.is_before_race:
            return self.race_number - 1
        else:
            return self.race_number

    # Begins the race if ready. (Writes a message if all racers are ready but an admin is not.)
    # Returns true on success
    @asyncio.coroutine
    def begin_if_ready(self):
        if self.race and self.all_racers_ready:
            yield from self.race.begin_race_countdown()
            return True     

    @asyncio.coroutine
    def enter_racer(self, member):
        for racer in self.match.racers:
            if int(racer.discord_id) == int(member.id):
                if racer in self.entered_racers:
                    yield from self.write('{0} is already here.'.format(member.mention))
                    return
                
                self.entered_racers.append(racer)
                    
                yield from self.write('{0} is here for the race.'.format(member.mention))
                yield from self.update_leaderboard()

                return

        yield from self.write('{0}: I do not recognize you as one of the racers in this match. Contact CoNDOR Staff (`.staff`) if this is in error.'.format(member.mention))

    @asyncio.coroutine  
    def record_race(self, cancelled=False):
        if self.race and self.race.start_time:
            self.recorded_race = True
            racer_1_time = -1
            racer_2_time = -1
            racer_1_finished = False
            racer_2_finished = False
            for racer in self.race.racer_list:
                if int(racer.id) == int(self.match.racer_1.discord_id):
                    if racer.is_finished:
                        racer_1_time = racer.time
                        racer_1_finished = True
                elif int(racer.id) == int(self.match.racer_2.discord_id):
                    if racer.is_finished:
                        racer_2_time = racer.time
                        racer_2_finished = True

            winner = 0
            if racer_1_finished and not racer_2_finished:
                winner = 1
            elif not racer_1_finished and racer_2_finished:
                winner = 2
            elif racer_1_finished and racer_2_finished:
                if racer_1_time < racer_2_time:
                    winner = 1
                elif racer_2_time < racer_1_time:
                    winner = 2

            if abs(racer_1_time - racer_2_time) <= (config.RACE_NOTIFY_IF_TIMES_WITHIN_SEC*100):
                race_number = self._cm.condordb.number_of_finished_races(self.match) + 1
                yield from self.client.send_message(self.necrobot.notifications_channel,
                    'Race number {0} has finished within {1} seconds in channel {2}. ({3} -- {4}, {5} -- {6})'.format(
                        race_number, config.RACE_NOTIFY_IF_TIMES_WITHIN_SEC, self.channel.mention,
                        self.match.racer_1.escaped_twitch_name, racetime.to_str(racer_1_time),
                        self.match.racer_2.escaped_twitch_name, racetime.to_str(racer_2_time)))

            self._cm.condordb.record_race(self.match, racer_1_time, racer_2_time, winner, self.race.race_info.seed, self.race.start_time.timestamp(), cancelled)

            if not cancelled:
                racer_1_member = self.necrobot.find_member_with_id(self.match.racer_1.discord_id)
                racer_2_member = self.necrobot.find_member_with_id(self.match.racer_2.discord_id)
                racer_1_mention = racer_1_member.mention if racer_1_member else ''
                racer_2_mention = racer_2_member.mention if racer_2_member else ''
                write_str = '{0}, {1}: The race is over, and has been recorded.'.format(racer_1_mention, racer_2_mention)
            else:
                write_str = 'Race cancelled.'
                
            yield from self.write(write_str)
            yield from self.write('If you wish to contest the previous race\'s result, use the `.contest` command. This marks the race as contested; CoNDOR Staff will be alerted, and will '
                                  'look into your race.')

            if self.played_all_races:
                #Send match ending event if all races have been played
                self.events.matchend(self.match.racer_1.twitch_name, self.match.racer_2.twitch_name)
                yield from self.record_match()
            else:
                yield from self.begin_new_race()

    @asyncio.coroutine
    def record_match(self):
        self._cm.condordb.record_match(self.match)
        yield from self._cm.condorsheet.record_match(self.match)
        yield from self.write('Match results recorded.')      
        yield from self.update_leaderboard()
Example #2
0
class RaceRoom(command.Module):

    def get_new_raceinfo():
        to_return = RaceInfo()
        to_return.seed_fixed = False
        to_return.seeded = True
        to_return.character = 'Cadence'
        to_return.descriptor = 'Condor Race'
        to_return.sudden_death = False
        to_return.flagplant = False
        to_return.seed = seedgen.get_new_seed()
        return to_return

    def __init__(self, condor_module, condor_match, race_channel):
        self.channel = race_channel                                 #The channel in which this race is taking place
        self.is_closed = False                                      #True if room has been closed
        self.match = condor_match

        self.race = None                                            #The current race
        self.recorded_race = False                                  #Whether the current race has been recorded

        self.entered_racers = []                                    #Racers that have typed .here in this channel
        self.before_races = True
        self.cancelling_racers = []                                 #Racers that have typed .cancel

        self._cm = condor_module           

        self.command_types = [command.DefaultHelp(self),
                              Here(self),
                              Ready(self),
                              Unready(self),
                              Done(self),
                              Undone(self),
                              Cancel(self),
                              #Forfeit(self),
                              #Unforfeit(self),
                              #Comment(self),
                              #Igt(self),
                              Time(self),
                              Contest(self),
                              ForceCancel(self),
                              ForceChangeWinner(self),
                              #ForceClose(self),
                              ForceForfeit(self),
                              #ForceForfeitAll(self),
                              ForceRecordRace(self),
                              ForceNewRace(self),
                              ForceCancelRace(self),
                              ForceRecordMatch(self),
                              #Kick(self),
                              ]

    @property
    def infostr(self):
        return 'Race'

    @property
    def client(self):
        return self._cm.necrobot.client

    @property
    def necrobot(self):
        return self._cm.necrobot

    @property
    def condordb(self):
        return self._cm.condordb

    #True if the user has admin permissions for this race
    def is_race_admin(self, member):
        admin_roles = self._cm.necrobot.admin_roles
        for role in member.roles:
            if role in admin_roles:
                return True
        
        return False

    # Set up the leaderboard etc. Should be called after creation; code not put into __init__ b/c coroutine
    @asyncio.coroutine
    def initialize(self, users_to_mention=[]):
        yield from self.update_leaderboard()
        asyncio.ensure_future(self.countdown_to_match_start())

    # Write text to the raceroom. Return a Message for the text written
    @asyncio.coroutine
    def write(self, text):
        return self.client.send_message(self.channel, text)

    # Write text to the bot_notifications channel.
    @asyncio.coroutine
    def alert_staff(self, text):
        return self.client.send_message(self._cm.necrobot.notifications_channel, text)

    # Register a racer as wanting to cancel
    @asyncio.coroutine
    def wants_to_cancel(self, member):
        for racer in self.match.racers:
            if int(racer.discord_id) == int(member.id) and not racer in self.cancelling_racers:
                self.cancelling_racers.append(racer)

        if len(self.cancelling_racers) == 2:
            yield from self.cancel_race()
            return True
        else:
            return False

    # Cancel the race
    @asyncio.coroutine
    def cancel_race(self):
        if self.race and not self.race.is_before_race:
            self.cancelling_racers = []
            yield from self.race.cancel()
            yield from self.record_race(cancelled=True)
            yield from self.write('The current race was cancelled.')
        else:
            self.cancelling_racers = []
            race_number = int(self._cm.condordb.largest_recorded_race_number(self.match))
            if race_number > 0:
                self.condordb.cancel_race(self.match, race_number)
                yield from self.write('The previous race was cancelled.'.format(race_number))
                yield from self.update_leaderboard()                  
                

    #Updates the leaderboard
    @asyncio.coroutine
    def update_leaderboard(self):
        if self.race or self.match.time_until_match.total_seconds() < 0:
            topic = '``` \n'
            topic += 'Necrodancer World Cup Match (Cadence Seeded)\n'
            max_name_len = 0
            for racer in self.match.racers:
                max_name_len = max(max_name_len, len(racer.discord_name))
            for racer in self.match.racers:
                wins = self._cm.condordb.number_of_wins(self.match, racer, count_draws=True)
                topic += '     ' + racer.discord_name + (' ' * (max_name_len - len(racer.discord_name))) + ' --- Wins: {0}\n'.format(str(round(wins,1) if wins % 1 else int(wins)))

            race_number = self._cm.condordb.number_of_finished_races(self.match) + 1
            if race_number > config.RACE_NUMBER_OF_RACES:
                topic += 'Match complete. \n'
            else:
                topic += 'Current race: #{}\n'.format(race_number)
                if self.race:
                    topic += self.race.leaderboard

            topic += '\n ```'
            asyncio.ensure_future(self.client.edit_channel(self.channel, topic=topic))
        else:
            topic_str = '``` \n'
            minutes_until_match = int( (self.match.time_until_match.total_seconds() + 30) // 60 )
            topic_str += 'The race is scheduled to begin in {0} minutes! Please let the bot know you\'re here by typing .here. \n\n'.format(minutes_until_match)

            waiting_str = ''
            if self.match.racer_1 not in self.entered_racers:
                waiting_str += self.match.racer_1.twitch_name + ', '
            if self.match.racer_2 not in self.entered_racers:
                waiting_str += self.match.racer_2.twitch_name + ', '

            topic_str += 'Still waiting for .here from: {0} \n'.format(waiting_str[:-2]) if waiting_str else 'Both racers are here!\n'

            topic_str += '```'
                
            asyncio.ensure_future(self.client.edit_channel(self.channel, topic=topic_str))

    @asyncio.coroutine
    def alert_racers(self, send_pm=False):
        member_1 = self._cm.necrobot.find_member_with_id(self.match.racer_1.discord_id)
        member_2 = self._cm.necrobot.find_member_with_id(self.match.racer_2.discord_id)

        alert_str = ''
        if member_1:
            alert_str += member_1.mention + ', '
        if member_2:
            alert_str += member_2.mention + ', '

        minutes_until_match = int( (self.match.time_until_match.total_seconds() + 30) // 60 )
        if alert_str:
            yield from self.write('{0}: The match is scheduled to begin in {1} minutes.'.format(alert_str[:-2], minutes_until_match))

        if send_pm:
            if member_1:
                yield from self.client.send_message(member_1, '{0}: Your match with {1} is scheduled to begin in {2} minutes.'.format(member_1.mention, self.match.racer_2.escaped_twitch_name, minutes_until_match))
            if member_2:
                yield from self.client.send_message(member_2, '{0}: Your match with {1} is scheduled to begin in {2} minutes.'.format(member_2.mention, self.match.racer_1.escaped_twitch_name, minutes_until_match))

    @asyncio.coroutine
    def countdown_to_match_start(self):        
        time_until_match = self.match.time_until_match
        asyncio.ensure_future(self.constantly_update_leaderboard())

        if time_until_match < datetime.timedelta(seconds=0):
            if not self.played_all_races:
                yield from self.write('I believe that I was just restarted; an error may have occurred. I am beginning a new race and attempting to pick up this ' \
                                      'match where we left off. If this is an error, or if there are unrecorded races, please contact CoNDOR Staff (`.staff`).')
                yield from self.begin_new_race()
        else:
            pm_warning = datetime.timedelta(minutes=30)
            first_warning = datetime.timedelta(minutes=15)
            alert_staff_warning = datetime.timedelta(minutes=5)

            if time_until_match > pm_warning:
                yield from asyncio.sleep( (time_until_match - pm_warning).total_seconds() )
                if not self.race:
                    yield from self.alert_racers(send_pm=True)                

            time_until_match = self.match.time_until_match            
            if time_until_match > first_warning:
                yield from asyncio.sleep( (time_until_match - first_warning).total_seconds() )
                if not self.race:
                    yield from self.alert_racers()

            time_until_match = self.match.time_until_match
            if time_until_match > alert_staff_warning:
                yield from asyncio.sleep( (time_until_match - alert_staff_warning).total_seconds() )

            # this is done at the alert_staff_warning, unless this function was called after the alert_staff_warning, in which case do it immediately
            if not self.race:
                yield from self.alert_racers()            
                for racer in self.match.racers:
                    if racer not in self.entered_racers:
                        discord_name = ''
                        if racer.discord_name:
                            discord_name = ' (Discord name: {0})'.format(racer.discord_name)
                        minutes_until_race = int( (self.match.time_until_match.total_seconds() + 30) // 60)
                        yield from self.alert_staff('Alert: {0}{1} has not yet shown up for their match, which is scheduled in {2} minutes.'.format(racer.escaped_twitch_name, discord_name, minutes_until_race))

                yield from self._cm.post_match_alert(self.match)

            yield from asyncio.sleep(self.match.time_until_match.total_seconds())

            if not self.race:
                yield from self.begin_new_race()

    @asyncio.coroutine
    def constantly_update_leaderboard(self):
        while self.match.time_until_match.total_seconds() > 0:
            asyncio.ensure_future(self.update_leaderboard())
            yield from asyncio.sleep(30)

    @asyncio.coroutine
    def begin_new_race(self):
        self.cancelling_racers = []
        self.before_races = False
        self.race = Race(self, RaceRoom.get_new_raceinfo())
        yield from self.race.initialize()
        self.recorded_race = False
        
        for racer in self.match.racers:
            racer_as_member = self._cm.necrobot.find_member_with_id(racer.discord_id)
            if racer_as_member:
                yield from self.race.enter_racer(racer_as_member)
            else:
                yield from self.write('Error: Couldn\'t find the racer {0}. Please contact CoNDOR Staff (`.staff`).'.format(racer.escaped_twitch_name))
                
        yield from self.update_leaderboard()

        race_number = int(self._cm.condordb.number_of_finished_races(self.match) + 1)
        race_str = '{}th'.format(race_number)
        if race_number == int(1):
            race_str = 'first'
        elif race_number == int(2):
            race_str = 'second'
        elif race_number == int(3):
            race_str = 'third'
            
        yield from self.write('Please input the seed ({1}) and type `.ready` when you are ready for the {0} race. '\
                              'When both racers `.ready`, the race will begin.'.format(race_str, self.race.race_info.seed))

    # Returns true if all racers are ready
    @property
    def all_racers_ready(self):
        return self.race and self.race.num_not_ready == 0

    @property
    def played_all_races(self):
        if self.match.is_best_of:
            return self._cm.condordb.number_of_wins_of_leader(self.match) >= (self.match.number_of_races//2 + 1)
        else:
            return self._cm.condordb.number_of_finished_races(self.match) >= self.match.number_of_races

    @property
    def race_to_contest(self):
        if not self.race:
            return 0

        if self.race.is_before_race:
            return self.race_number - 1
        else:
            return self.race_number

    # Begins the race if ready. (Writes a message if all racers are ready but an admin is not.)
    # Returns true on success
    @asyncio.coroutine
    def begin_if_ready(self):
        if self.race and self.all_racers_ready:
            yield from self.race.begin_race_countdown()
            return True     

    @asyncio.coroutine
    def enter_racer(self, member):
        for racer in self.match.racers:
            if int(racer.discord_id) == int(member.id):
                if racer in self.entered_racers:
                    yield from self.write('{0} is already here.'.format(member.mention))
                    return
                
                self.entered_racers.append(racer)
                    
                yield from self.write('{0} is here for the race.'.format(member.mention))
                yield from self.update_leaderboard()

                return

        yield from self.write('{0}: I do not recognize you as one of the racers in this match. Contact CoNDOR Staff (`.staff`) if this is in error.'.format(member.mention))

    @asyncio.coroutine  
    def record_race(self, cancelled=False):
        if self.race and self.race.start_time:
            self.recorded_race = True
            racer_1_time = -1
            racer_2_time = -1
            racer_1_finished = False
            racer_2_finished = False
            for racer in self.race.racer_list:
                if int(racer.id) == int(self.match.racer_1.discord_id):
                    if racer.is_finished:
                        racer_1_time = racer.time
                        racer_1_finished = True
                elif int(racer.id) == int(self.match.racer_2.discord_id):
                    if racer.is_finished:
                        racer_2_time = racer.time
                        racer_2_finished = True

            winner = 0
            if racer_1_finished and not racer_2_finished:
                winner = 1
            elif not racer_1_finished and racer_2_finished:
                winner = 2
            elif racer_1_finished and racer_2_finished:
                if racer_1_time < racer_2_time:
                    winner = 1
                elif racer_2_time < racer_1_time:
                    winner = 2

            if abs(racer_1_time - racer_2_time) <= (config.RACE_NOTIFY_IF_TIMES_WITHIN_SEC*100):
                race_number = self._cm.condordb.number_of_finished_races(self.match) + 1
                yield from self.client.send_message(self.necrobot.notifications_channel,
                    'Race number {0} has finished within {1} seconds in channel {2}. ({3} -- {4}, {5} -- {6})'.format(
                        race_number, config.RACE_NOTIFY_IF_TIMES_WITHIN_SEC, self.channel.mention,
                        self.match.racer_1.escaped_twitch_name, racetime.to_str(racer_1_time),
                        self.match.racer_2.escaped_twitch_name, racetime.to_str(racer_2_time)))

            self._cm.condordb.record_race(self.match, racer_1_time, racer_2_time, winner, self.race.race_info.seed, self.race.start_time.timestamp(), cancelled)

            if not cancelled:
                racer_1_member = self.necrobot.find_member_with_id(self.match.racer_1.discord_id)
                racer_2_member = self.necrobot.find_member_with_id(self.match.racer_2.discord_id)
                racer_1_mention = racer_1_member.mention if racer_1_member else ''
                racer_2_mention = racer_2_member.mention if racer_2_member else ''
                write_str = '{0}, {1}: The race is over, and has been recorded.'.format(racer_1_mention, racer_2_mention)
            else:
                write_str = 'Race cancelled.'
                
            yield from self.write(write_str)
            yield from self.write('If you wish to contest the previous race\'s result, use the `.contest` command. This marks the race as contested; CoNDOR Staff will be alerted, and will '
                                  'look into your race.')

            if self.played_all_races:
                yield from self.record_match()
            else:
                yield from self.begin_new_race()

    @asyncio.coroutine
    def record_match(self):
        self._cm.condordb.record_match(self.match)
        yield from self._cm.condorsheet.record_match(self.match)
        yield from self.write('Match results recorded.')      
        yield from self.update_leaderboard()