Пример #1
0
class triviabot(irc.IRCClient):
    '''
    This is the irc bot portion of the trivia bot.

    It implements the whole program so is kinda big. The algorithm is
    implemented by a series of callbacks, initiated by an admin on the
    server.
    '''

    def __init__(self):
        self._answer = Answer()
        self._question = ''
        self._scores = {}
        self._clue_number = 0
        self._admins = list(config.ADMINS)
        self._admins.append(config.OWNER)
        self._game_channel = config.GAME_CHANNEL
        self._current_points = 5
        self._questions_dir = config.Q_DIR
        self._lc = LoopingCall(self._play_game)
        self._quit = False
        self._restarting = False
        self._load_game()
        self._votes = 0
        self._voters = []

    def _get_nickname(self):
        return self.factory.nickname

    nickname = property(_get_nickname)

    def _get_lineRate(self):
        return self.factory.lineRate

    lineRate = property(_get_lineRate)

    def _cmsg(self, dest, msg):
        """
        Write a colorized message.
        """

        self.msg(dest, "{}{}".format(config.COLOR_CODE, msg))

    def _gmsg(self, msg):
        """
        Write a message to the channel playing the trivia game.
        """

        self._cmsg(self._game_channel, msg)

    def _play_game(self):
        '''
        Implements the main loop of the game.
        '''
        points = {0: 5,
                  1: 3,
                  2: 2,
                  3: 1
                  }
        if self._clue_number == 0:
            self._votes = 0
            self._voters = []
            self._get_new_question()
            self._current_points = points[self._clue_number]
            # Blank line.
            self._gmsg("")
            self._gmsg("Next question:")
            self._gmsg(self._question)
            self._gmsg("Clue: {}".format(self._answer.current_clue()))
            self._clue_number += 1
        # we must be somewhere in between
        elif self._clue_number < 4:
            self._current_points = points[self._clue_number]
            self._gmsg("Question:")
            self._gmsg(self._question)
            self._gmsg("Clue: {}".format(self._answer.give_clue()))
            self._clue_number += 1
        # no one must have gotten it.
        else:
            self._gmsg("No one got it. The answer was: {}"
                       .format(self._answer.answer))
            self._clue_number = 0
            self._get_new_question()
            # self._lc.reset()

    def signedOn(self):
        '''
        Actions to perform on signon to the server.
        '''
        self.join(self._game_channel)
        self.msg("NickServ", "identify {}".format(config.IDENT_STRING))
        print("Signed on as {}.".format(self.nickname))
        if self.factory.running:
            self._start(None, None, None)
        else:
            self._gmsg("Welcome to {}!".format(self._game_channel))
            self._gmsg("Have an admin start the game when you are ready.")
            self._gmsg("For how to use this bot, just say ?help or")
            self._gmsg("{} help.".format(self.nickname))

    def joined(self, channel):
        '''
        Callback runs when the bot joins a channel
        '''
        print("Joined {}.".format((channel)))

    def privmsg(self, user, channel, msg):
        '''
        Parses out each message and initiates doing the right thing
        with it.
        '''
        user, temp = user.split('!')
        print(user + " : " + channel + " : " + msg)
        # need to strip out non-printable characters if present.
        printable = string.printable
        msg = ''.join(filter(lambda x: x in printable, msg))

        # parses each incoming line, and sees if it's a command for the bot.
        try:
            if (msg[0] == "?"):
                command = msg.replace('?', '').split()[0]
                args = msg.replace('?', '').split()[1:]
                self.select_command(command, args, user, channel)
                return
            elif (msg.split()[0].find(self.nickname) == 0):
                command = msg.split()[1]
                args = msg.replace(self.nickname, '').split()[2:]
                self.select_command(command, args, user, channel)
                return
            # if not, try to match the message to the answer.
            else:
                if msg.lower().strip() == self._answer.answer.lower():
                    self._winner(user, channel)
                    self._save_game()
        except Exception as e:
            print(e)
            return

    def _winner(self, user, channel):
        '''
        Congratulates the winner for guessing correctly and assigns
        points appropriately, then signals that it was guessed.
        '''
        if channel != self._game_channel:
            self.msg(channel,
                     "I'm sorry, answers must be given in the game channel.")
            return
        self._gmsg("{} GOT IT!".format(user.upper()))
        self._gmsg("""If there was any doubt, the correct answer was: {}""".format(self._answer.answer))
        try:
            self._scores[user] += self._current_points
        except:
            self._scores[user] = self._current_points
        if self._current_points == 1:
            self._gmsg("{} point has been added to your score!"
                       .format(str(self._current_points)))
        else:
            self._gmsg("{} points have been added to your score!"
                       .format(str(self._current_points)))
        self._clue_number = 0
        self._get_new_question()

    def ctcpQuery(self, user, channel, msg):
        '''
        Responds to ctcp requests.
        Currently just reports them.
        '''
        print("CTCP recieved: " + user + ":" + channel +
              ": " + msg[0][0] + " " + msg[0][1])

    def _help(self, args, user, channel):
        '''
        Tells people how to use the bot.
        Replies differently if you are an admin or a regular user.
        Only responds to the user since there could be a game in
        progress.
        '''
        try:
            self._admins.index(user)
        except:
            self._cmsg(user, "I'm {}'s trivia bot.".format(config.OWNER))
            self._cmsg(user, "Commands: score, standings, giveclue, help, "
                       "next, source")
            return
        self._cmsg(user, "I'm {}'s trivia bot.".format(config.OWNER))
        self._cmsg(user, "Commands: score, standings, giveclue, help, next, "
                   "skip, source")
        self._cmsg(user, "Admin commands: die, set <user> <score>, start, stop, "
                   "save")

    def _show_source(self, args, user, channel):
        '''
        Tells people how to use the bot.
        Only responds to the user since there could be a game in
        progress.
        '''
        self._cmsg(user, 'My source can be found at: '
                   'https://github.com/rawsonj/triviabot')

    def select_command(self, command, args, user, channel):
        '''
        Callback that responds to commands given to the bot.

        Need to differentiate between priviledged users and regular
        users.
        '''
        # set up command dicts.
        unpriviledged_commands = {'score': self._score,
                                  'help': self._help,
                                  'source': self._show_source,
                                  'standings': self._standings,
                                  'giveclue': self._give_clue,
                                  'next': self._next_vote,
                                  'skip': self._next_question
                                  }
        priviledged_commands = {'die': self._die,
                                'restart': self._restart,
                                'set': self._set_user_score,
                                'start': self._start,
                                'stop': self._stop,
                                'save': self._save_game,
                                }
        print(command, args, user, channel)
        try:
            self._admins.index(user)
            is_admin = True
        except:
            is_admin = False

        # the following takes care of sorting out functions and
        # priviledges.
        if not is_admin and command in priviledged_commands:
            self.msg(channel, "{}: You don't tell me what to do."
                     .format(user))
            return
        elif is_admin and command in priviledged_commands:
            priviledged_commands[command](args, user, channel)
        elif command in unpriviledged_commands:
            unpriviledged_commands[command](args, user, channel)
        else:
            self.describe(channel, "{}looks at {} oddly."
                          .format(config.COLOR_CODE, user))

    def _next_vote(self, args, user, channel):
        '''Implements user voting for the next question.

        Need to keep track of who voted, and how many votes.

        '''
        if not self._lc.running:
            self._gmsg("We aren't playing right now.")
            return
        try:
            self._voters.index(user)
            self._gmsg("You already voted, {}, give someone else a chance to "
                       "hate this question".format(user))
            return
        except:
            if self._votes < 2:
                self._votes += 1
                self._voters.append(user)
                print(self._voters)
                self._gmsg("{}, you have voted. {} more votes needed to "
                           "skip.".format(user, str(3 - self._votes)))
            else:
                self._votes = 0
                self._voters = []
                self._next_question(None, None, None)

    def _start(self, args, user, channel):
        '''
        Starts the trivia game.

        TODO: Load scores from last game, if any.
        '''
        if self._lc.running:
            return
        else:
            self._lc.start(config.WAIT_INTERVAL)
            self.factory.running = True

    def _stop(self, *args):
        '''
        Stops the game and thanks people for playing,
        then saves the scores.
        '''
        if not self._lc.running:
            return
        else:
            self._lc.stop()
            self._gmsg('Thanks for playing trivia!')
            self._gmsg('Current rankings were:')
            self._standings(None, self._game_channel, None)
            self._gmsg('''Scores have been saved, and see you next game!''')
            self._save_game()
            self.factory.running = False

    def _save_game(self, *args):
        '''
        Saves the game to the data directory.
        '''
        with open(os.path.join(config.SAVE_DIR, 'scores.json'), 'w') as savefile:
            json.dump(self._scores, savefile)
            print("Scores have been saved.")

    def _load_game(self):
        '''
        Loads the running data from previous games.
        '''
        # ensure initialization
        self._scores = {}
        if not path.exists(config.SAVE_DIR):
            print("Save directory doesn't exist.")
            return
        try:
            with open(os.path.join(config.SAVE_DIR, 'scores.json'), 'r') as savefile:
                temp_dict = json.load(savefile)
        except:
            print("Save file doesn't exist.")
            return
        for name in temp_dict.keys():
            self._scores[str(name)] = int(temp_dict[name])
        print(self._scores)
        print("Scores loaded.")

    def _set_user_score(self, args, user, channel):
        '''
        Administrative action taken to adjust scores, if needed.
        '''
        try:
            self._scores[args[0]] = int(args[1])
        except:
            self._cmsg(user, args[0] + " not in scores database.")
            return
        self._cmsg(user, args[0] + " score set to " + args[1])

    def _die(self, *args):
        '''
        Terminates execution of the bot.
        '''
        self._quit = True
        self.quit(message='This is triviabot, signing off.')

    def _restart(self, *args):
        '''
        Restarts the bot
        '''
        self._restarting = True
        print('Restarting')
        self.quit(message='Triviabot restarting.')

    def connectionLost(self, reason):
        '''
        Called when connection is lost
        '''
        global reactor
        if self._restarting:
            try:
                execl(sys.executable, *([sys.executable]+sys.argv))
            except Exception as e:
                print("Failed to restart: {}".format(e))
        if self._quit:
            reactor.stop()

    def _score(self, args, user, channel):
        '''
        Tells the user their score.
        '''
        try:
            self._cmsg(user, "Your current score is: {}"
                       .format(str(self._scores[user])))
        except:
            self._cmsg(user, "You aren't in my database.")

    def _next_question(self, args, user, channel):
        '''
        Administratively skips the current question.
        '''
        if not self._lc.running:
            self._gmsg("We are not playing right now.")
            return
        self._gmsg("Question has been skipped. The answer was: {}".format(self._answer.answer))
        self._clue_number = 0
        self._lc.stop()
        self._lc.start(config.WAIT_INTERVAL)

    def _standings(self, args, user, channel):
        '''
        Tells the user the complete standings in the game.

        TODO: order them.
        '''
        self._cmsg(user, "The current trivia standings are: ")
        sorted_scores = sorted(self._scores.iteritems(), key=lambda (k, v): (v, k), reverse=True)
        for rank, (player, score) in enumerate(sorted_scores, start=1):
            formatted_score = "{}: {}: {}".format(rank, player, score)
            self._cmsg(user, formatted_score)

    def _give_clue(self, args, user, channel):
        if not self._lc.running:
            self._gmsg("we are not playing right now.")
            return
        self._cmsg(channel, "Question: ")
        self._cmsg(channel, self._question)
        self._cmsg(channel, "Clue: " + self._answer.current_clue())

    def _get_new_question(self):
        '''
        Selects a new question from the questions directory and
        sets it.
        '''
        damaged_question = True
        while damaged_question:
            # randomly select file
            filename = choice(listdir(self._questions_dir))
            fd = open(os.path.join(config.Q_DIR, filename))
            lines = fd.read().splitlines()
            myline = choice(lines)
            fd.close()
            try:
                self._question, temp_answer = myline.split('`')
            except ValueError:
                print("Broken question:")
                print(myline)
                continue
            self._answer.set_answer(temp_answer.strip())
            damaged_question = False
Пример #2
0
class triviabot(irc.IRCClient):
    '''
    This is the irc bot portion of the trivia bot.

    It implements the whole program so is kinda big. The algorithm is
    implemented by a series of callbacks, initiated by an admin on the
    server.
    '''
    def __init__(self):
        self._answer = Answer()
        self._question = ''
        self._scores = {}
        self._userlist = {}
        self._clue_number = 0
        self._admins = list(config.ADMINS)
        self._game_channel = config.GAME_CHANNEL
        self._current_points = 5
        self._questions_dir = config.Q_DIR
        self._lc = LoopingCall(self._play_game)
        self._restarting = False
        self._quit = False
        self._load_game()
        self._votes = 0
        self._voters = []
        self._no_plays = 0

    def _get_nickname(self):
        return self.factory.nickname

    nickname = property(_get_nickname)

    def _get_realname(self):
        return self.factory.realname

    realname = property(_get_realname)

    def _get_lineRate(self):
        return self.factory.lineRate

    lineRate = property(_get_lineRate)

    def _gmsg(self, msg):
        """
        Write a message to the channel playing the trivia game.
        """
        self.msg(self._game_channel, msg)

    def _play_game(self):
        '''
        Implements the main loop of the game.
        '''
        self._points = {0: 100,
                        1: 75,
                        2: 50,
                        3: 25
                        }
        self._cluelabels= {0: 'Clue:',
                           1: '2nd Clue:',
                           2: '3rd Clue:',
                           3: 'Final Clue:'
                           }
        if self._clue_number == 0:
            self._votes = 0
            self._voters = []
            self._get_new_question()
            self._current_points = self._points[self._clue_number]
            # Blank line.
            self._gmsg("")
            self._gmsg("Next question:")
            self._gmsg(self._question)
            self._gmsg("%s %s  Points: %d" % (self._cluelabels[self._clue_number],
                       self._answer.current_clue(), self._current_points))
            self._clue_number += 1
        # we must be somewhere in between
        elif self._clue_number < 4:
            self._current_points = self._points[self._clue_number]
            self._gmsg('%s %s  Points: %d' % (self._cluelabels[self._clue_number],
                       self._answer.give_clue(), self._current_points))
            self._clue_number += 1
        # no one must have gotten it.
        else:
            self._gmsg('No one got it. The answer was: %s' %
                       self._answer.answer)
            self._clue_number = 0
            self._no_plays += 1
            # Stop gameplay after 10 questions of no activity
            if (self._no_plays == 10):
                self._gmsg('It appears I am talking to myself now!')
                self._stop()
            else:
                self._get_new_question()

    def irc_RPL_NAMREPLY(self, *nargs):
        '''
        Called when we get a reply to NAMES
        Using this for tracking user modes, in a simplistic manner
        '''
        if (nargs[1][2] != self._game_channel): return
        users = nargs[1][3].split()
        for u in users:
            split = re.split('(\~|\&|\@|\%|\+)', u)
            try:
                mode = split[1]
                user = split[2]
            except IndexError:
                mode = ''
                user = split[0]
            mode = mode.replace('+', 'voice')
            mode = mode.replace('%', 'halfop')
            mode = mode.replace('@', 'op')
            mode = mode.replace('&', 'admin')
            mode = mode.replace('~', 'owner')
            # This is for us joining the channel and re-checking after mode changes
            try:
                self._userlist[user]
                self._userlist['modes'] = (mode,)
            except:
                self._userlist[user] = {}
                self._userlist[user]['wins'] = 0
                self._userlist[user]['modes'] = (mode,)
                self._userlist[user]['strikes'] = 0

    def signedOn(self):
        '''
        Actions to perform on signon to the server.
        '''
        try:
            config.IDENT_PASS
            self.msg('NickServ', 'identify %s' % config.IDENT_PASS)
        except:
            pass
        self.mode(self.nickname, True, config.DEFAULT_MODES)
        print("Signed on as %s." % (self.nickname,))
        self.join(self._game_channel)
        if self.factory.running:
            self._start(None, None, None)
        else:
            self._gmsg('Welcome to %s!' % self._game_channel)
            self._gmsg("For how to use this bot, just say ?help or '%s help'." % self.nickname)

    def joined(self, channel):
        '''
        Callback runs when the bot joins a channel
        A join automatically receives a NAMES reply, for user listing
        '''
        print("Joined %s." % (channel,))
        if (channel != self._game_channel):
            self.leave(channel, 'No!')
            return

    def kickedFrom(self, channel, kicker, message):
        '''
        If we get kicked from gthe game channel,
        attempt to rejoin.
        '''
        print("Kicked from %s by %s: %s" % (channel, kicker, message))
        if (channel != self._game_channel):
            return
        self.join(self._game_channel)

    def userJoined(self, user, channel):
        '''
        Callback for when other users join the channel
        '''
        if channel != self._game_channel: return
        # Add user to userlist, track wins, modes, and strikes of user
        self._userlist[user] = {}
        self._userlist[user]['wins'] = 0
        self._userlist[user]['modes'] = ('',)
        self._userlist[user]['strikes'] = 0
        # If admin, don't send intro notice and op them
        try:
            self._admins.index(user)
            self.mode(channel, True, 'o', user=user)
            self._userlist[user]['modes'].append('op')
        except:
            self.notice(user, "Welcome to %s!" % self._game_channel)
            self.notice(user, "For how to use this bot, just say ?help or '%s help'." % self.nickname)
            if not self.factory.running:
                self.notice(user, "Just say ?start to start the game when you are ready.")

    def userLeft(self, user, channel):
        '''
        Called when a user leaves the game channel
        '''
        if channel != self._game_channel: return
        if user in self._userlist:
            del self._userlist[user]

    def userQuit(self, user, quitMessage):
        '''
        Called when a user quits
        '''
        if channel != self._game_channel: return
        if user in self._userlist:
            del self._userlist[user]

    def userKicked(self, kickee, channel, kicker, message):
        '''
        Called when a user is kicked from the game channel
        '''
        if channel != self._game_channel: return
        if kickee in self._userlist:
            del self._userlist[kickee]

    def userRenamed(self, oldname, newname):
        '''
        Called when a user changes nicknames
        '''
        if oldname in self._userlist:
            self._userlist[newname] = self._userlist.pop(oldname)

    def modeChanged(self, user, channel, set, modes, args):
        '''
        Called when a mode change is seen
        '''
        if channel != self._game_channel: return
        #print('MODE: %s : direction %d : %s and %s' % (user, set, modes, args))
        # Check if 'our' users are part of a mode change, re-run NAMES
        user_change = False
        for u in self._userlist:
            if (u in args):
                user_change = True
                break
        if (user_change == False): return
        self.sendLine('NAMES %s' % channel)

    def privmsg(self, user, channel, msg):
        '''
        Parses out each message and initiates doing the right thing
        with it.
        '''
        user, temp = user.split('!')
        #print(user+" : "+channel+" : "+msg)
        # ignore STATUSMSGs, lazy check
        if (not channel[0] == "#"):
            return
        # need to strip off colors if present.
        try:
            while not msg[0].isalnum() and not msg[0] == '?':
                msg = msg[1:]
        except IndexError:
            return

        # parses each incoming line, and sees if it's a command for the bot.
        try:
            if (msg[0] == "?"):
                command = msg.replace('?', '').split()[0]
                args = msg.replace('?', '').split()[1:]
                self.select_command(command, args, user, channel)
                return
            elif (msg.split()[0].find(self.nickname) == 0):
                command = msg.split()[1]
                args = msg.replace(self.nickname, '').split()[2:]
                self.select_command(command, args, user, channel)
                return
            # if not, try to match the message to the answer.
            else:
                if msg.lower().strip() == self._answer.answer.lower():
                    self._no_plays = 0
                    self._winner(user, channel)
                    self._save_game()
        except:
            return
        # Assuming this is gameplay
        self._no_plays = 0

    def _winner(self, user, channel):
        '''
        Congratulates the winner for guessing correctly and assigns
        points appropriately, then signals that it was guessed.
        '''
        if channel != self._game_channel:
            self.msg(channel,
                     "I'm sorry, answers must be given in the game channel.")
            return
        self._gmsg("%s GOT IT!" % user)
        try:
            self._scores[user] += self._current_points
        except:
            self._scores[user] = self._current_points
        self._gmsg("%s points have been added to your score!" %
                   str(self._current_points))
        self._clue_number = 0
        self._get_new_question()
        self._userlist[user]['wins'] += 1
        if (self._userlist[user]['wins'] == 2):
            self.mode(channel, True, 'v', user=user)
            self._gmsg('Five correct answers! That earns you a voice!')
            self._userlist[user]['modes'].append('voice')
        elif (self._userlist[user]['wins'] == 4):
            self.mode(channel, True, 'h', user=user)
            self._gmsg('Another fifteen correct answers, have some halfops!')
            self._userlist[user]['modes'].append('halfop')

    def ctcpQuery(self, user, channel, msg):
        '''
        Responds to ctcp requests.
        '''
        msg = str(msg[0][0]).lower()
        user = (user.split("!"))[0]
        if (msg == 'action'): return
        print("CTCP from %s : %s" % (user, msg))
        if (msg == 'version'):
            self.notice(user, "CTCP VERSION: Trivia Bot!")
        elif (msg == 'time'):
            self.notice(user, "CTCP TIME: Trivia Time!")
        elif (msg == 'ping'):
            self.notice(user, "CTCP PING: Trivia Pong!")
        else:
            self.notice(user, "Unknown CTCP Query!")

    def _help(self, args, user, channel):
        '''
        Tells people how to use the bot.
        Replies differently if you are an admin or a regular user.
        Only responds to the user since there could be a game in
        progress.
        '''
        try:
            self._admins.index(user)
        except:
            self.notice(user, "Commands: start, stop, score, standings, "
                       "question, clue, help, next, source")
            return
        self.notice(user, "Commands: start, stop, score, standings, "
                   "question, clue, help, next, source")
        self.notice(user, "Admin commands: skip, restart, die, "
                   "set <user> <score>, save")

    def _show_source(self, args, user, channel):
        '''
        Tells people how to use the bot.
        Only responds to the user since there could be a game in
        progress.
        '''
        self.notice(user, 'My source can be found at: '
                   'https://github.com/genius3000/triviabot')
        self.notice(user, 'Original source can be found at: '
                   'https://github.com/rawsonj/triviabot')

    def select_command(self, command, args, user, channel):
        '''
        Callback that responds to commands given to the bot.

        Need to differentiate between priviledged users and regular
        users.
        '''
        # set up command dicts.
        unpriviledged_commands = {'score': self._score,
                                  'help': self._help,
                                  'start': self._start,
                                  'stop': self._stop,
                                  'source': self._show_source,
                                  'standings': self._standings,
                                  'question': self._show_question,
                                  'clue': self._give_clue,
                                  'next': self._next_vote,
                                  }
        priviledged_commands = {'skip': self._next_question,
                                'restart': self._restart,
                                'die': self._die,
                                'set': self._set_user_score,
                                'save': self._save_game,
                                }
        print(command, args, user, channel)
        try:
            self._admins.index(user)
            is_admin = True
        except:
            is_admin = False
        command = command.lower()
        # the following takes care of sorting out functions and
        # priviledges.
        if not is_admin and command in priviledged_commands.keys():
            self.msg(channel, "%s: You don't tell me what to do." % user)
            self._userlist[user]['strikes'] += 1
            if (self._userlist[user]['strikes'] == 5):
                self.kick(channel, user, "You've earned five strikes, be gone!")
            elif ('halfop' in self._userlist[user]['modes']):
                self.mode(channel, False, 'h', user=user)
                self._userlist[user]['modes'].remove('halfop')
            elif ('voice' in self._userlist[user]['modes']):
                self.mode(channel, False, 'v', user=user)
                self._userlist[user]['modes'].remove('voice')
            return
        elif is_admin and command in priviledged_commands.keys():
            priviledged_commands[command](args, user, channel)
        elif command in unpriviledged_commands.keys():
            unpriviledged_commands[command](args, user, channel)
        else:
            self.describe(channel, 'looks at %s oddly.' % user)

    def _next_vote(self, args, user, channel):
        '''Implements user voting for the next question.

        Need to keep track of who voted, and how many votes.

        '''
        if not self._lc.running:
            self._gmsg("We aren't playing right now.")
            return
        try:
            self._voters.index(user)
            self._gmsg("You already voted, %s, give someone else a chance to "
                       "hate this question" % user)
            return
        except:
            if self._votes < 2:
                self._votes += 1
                self._voters.append(user)
                print(self._voters)
                self._gmsg("%s, you have voted. %s more votes needed to "
                           "skip." % (user, str(3-self._votes)))
            else:
                self._votes = 0
                self._voters = []
                self._next_question(None, None, None)

    def _start(self, args, user, channel):
        '''
        Starts the trivia game.

        TODO: Load scores from last game, if any.
        '''
        if self._lc.running:
            return
        else:
            self._get_new_question()
            self._clue_number = 0
            self._no_plays = 0
            self._lc.start(config.WAIT_INTERVAL)
            self.factory.running = True

    def _stop(self, *args):
        '''
        Stops the game and thanks people for playing,
        then saves the scores.
        '''
        if not self._lc.running:
            return
        else:
            self._lc.stop()
            self._gmsg('Thanks for playing trivia!')
            self._gmsg('Current rankings are:')
            self._standings(None, None, self._game_channel)
            self._gmsg('Scores have been saved, and see you next game!')
            self._save_game()
            self.factory.running = False

    def _save_game(self, *args):
        '''
        Saves the game to the data directory.
        '''
        if not path.exists(config.SAVE_DIR):
            makedirs(config.SAVE_DIR)
        with open(config.SAVE_DIR+'scores.json', 'w') as savefile:
            json.dump(self._scores, savefile)
            print("Scores have been saved.")

    def _load_game(self):
        '''
        Loads the running data from previous games.
        '''
        # ensure initialization
        self._scores = {}
        if not path.exists(config.SAVE_DIR):
            print("Save directory doesn't exist.")
            return
        try:
            with open(config.SAVE_DIR+'scores.json', 'r') as savefile:
                temp_dict = json.load(savefile)
        except:
            print("Save file doesn't exist.")
            return
        for name in temp_dict.keys():
            self._scores[str(name)] = int(temp_dict[name])
        print(self._scores)
        print("Scores loaded.")

    def _set_user_score(self, args, user, channel):
        '''
        Administrative action taken to adjust scores, if needed.
        '''
        try:
            self._scores[args[0]] = int(args[1])
        except:
            self.notice(user, args[0]+" not in scores database.")
            return
        self.notice(user, args[0]+" score set to "+args[1])

    def _restart(self, *args):
        '''
        Restart the bot.
        '''
        self._restarting = True
        self.quit('Restarting eh')

    def _die(self, *args):
        '''
        Terminates execution of the bot.
        '''
        self._quit = True
        self.quit(config.DEFAULT_QUIT)

    def connectionLost(self, reason):
        '''
        Called when connection is lost
        '''
        global reactor
        if self._restarting:
            execl(sys.executable, *([sys.executable]+sys.argv))
        elif self._quit:
            reactor.stop()

    def _score(self, args, user, channel):
        '''
        Tells the user their score.
        '''
        try:
            self.notice(user, "Your current score is: %s" %
                       str(self._scores[user]))
        except:
            self.notice(user, "You aren't in my database.")

    def _next_question(self, args, user, channel):
        '''
        Administratively skips the current question.
        '''
        if not self._lc.running:
            self._gmsg("We are not playing right now.")
            return
        self._gmsg("Question has been skipped. The answer was: %s" %
                   self._answer.answer)
        self._clue_number = 0
        self._lc.stop()
        self._lc.start(config.WAIT_INTERVAL)

    def _standings(self, args, user, channel):
        '''
        Tells the user the complete standings in the game.
        '''
        if channel == self.nickname:
            dst = user
        else:
            if channel != self._game_channel: return
            dst = channel
        score_list = []
        if not user is None:
            self.notice(dst, "The current trivia standings are: ")
        sorted_scores = sorted(self._scores.iteritems(), key=lambda x:x[1], reverse=True)
        for rank, (player, score) in enumerate(sorted_scores, start=1):
            formatted_score = "#%s: %s with %s points" % (rank, player, score)
            score_list.append(formatted_score)
        # Will have to split this at a certain length later
        self.notice(dst, ", ".join([str(player) for player in score_list]))

    def _show_question(self, args, user, channel):
        if not self._lc.running:
            self._gmsg("We are not playing right now.")
            return
        self._gmsg("Current question: %s" % self._question)

    def _give_clue(self, args, user, channel):
        if not self._lc.running:
            self._gmsg("We are not playing right now.")
            return
        # Just stop and start gameplay timer. It will give a new clue
        # and wait another 'WAIT_INTERVAL' until the next clue
        self._lc.stop()
        self._lc.start(config.WAIT_INTERVAL)

    def _get_new_question(self):
        '''
        Selects a new question from the questions directory and
        sets it.
        '''
        damaged_question = True
        while damaged_question:
            # randomly select file
            filename = choice(listdir(self._questions_dir))
            fd = open(config.Q_DIR+filename)
            lines = fd.read().splitlines()
            myline = choice(lines)
            fd.close()
            try:
                self._question, temp_answer = myline.split('`')
            except ValueError:
                print("Broken question:")
                print(myline)
                continue
            self._answer.set_answer(temp_answer.strip())
            damaged_question = False
Пример #3
0
class triviabot(irc.IRCClient):
    '''
    This is the irc bot portion of the trivia bot.

    It implements the whole program so is kinda big. The algorithm is
    implemented by a series of callbacks, initiated by an admin on the
    server.
    '''
    def __init__(self):
        self._answer = Answer()
        self._question = ''
        self._scores = {}
        self._clue_number = 0
        self._admins = list(config.ADMINS)
        self._game_channel = config.GAME_CHANNEL
        self._current_points = 5
        self._questions_dir = config.Q_DIR
        self._lc = LoopingCall(self._play_game)
        self._load_game()
        self._votes = 0
        self._voters = []

    def _get_nickname(self):
        return self.factory.nickname

    nickname = property(_get_nickname)

    def _get_lineRate(self):
        return self.factory.lineRate

    lineRate = property(_get_lineRate)

    def _cmsg(self, dest, msg):
        """
        Write a colorized message.
        """

        self.msg(dest, "%s%s" % (config.COLOR_CODE, msg))

    def _gmsg(self, msg):
        """
        Write a message to the channel playing the trivia game.
        """

        self._cmsg(self._game_channel, msg)

    def _play_game(self):
        '''
        Implements the main loop of the game.
        '''
        points = {0: 5,
                  1: 3,
                  2: 2,
                  3: 1
                  }
        if self._clue_number == 0:
            self._votes = 0
            self._voters = []
            self._get_new_question()
            self._current_points = points[self._clue_number]
            # Blank line.
            self._gmsg("")
            self._gmsg("Next question:")
            self._gmsg(self._question)
            self._gmsg("Clue: %s" % self._answer.current_clue())
            self._clue_number += 1
        # we must be somewhere in between
        elif self._clue_number < 4:
            self._current_points = points[self._clue_number]
            self._gmsg("Question:")
            self._gmsg(self._question)
            self._gmsg('Clue: %s' % self._answer.give_clue())
            self._clue_number += 1
        # no one must have gotten it.
        else:
            self._gmsg('No one got it. The answer was: %s' %
                       self._answer.answer)
            self._clue_number = 0
            self._get_new_question()
            # self._lc.reset()

    def signedOn(self):
        '''
        Actions to perform on signon to the server.
        '''
        self.join(self._game_channel)
        self.msg('NickServ', 'identify %s' % config.IDENT_STRING)
        print("Signed on as %s." % (self.nickname,))
        if self.factory.running:
            self._start(None, None, None)
        else:
            self._gmsg('Welcome to %s!' % self._game_channel)
            self._gmsg("Have an admin start the game when you are ready.")
            self._gmsg("For how to use this bot, just say ?help or")
            self._gmsg("%s help." % self.nickname)

    def joined(self, channel):
        '''
        Callback runs when the bot joins a channel
        '''
        print("Joined %s." % (channel,))

    def privmsg(self, user, channel, msg):
        '''
        Parses out each message and initiates doing the right thing
        with it.
        '''
        user, temp = user.split('!')
        print(user+" : "+channel+" : "+msg)
        # need to strip off colors if present.
        try:
            while not msg[0].isalnum() and not msg[0] == '?':
                msg = msg[1:]
        except IndexError:
            return

        # parses each incoming line, and sees if it's a command for the bot.
        try:
            if (msg[0] == "?"):
                command = msg.replace('?', '').split()[0]
                args = msg.replace('?', '').split()[1:]
                self.select_command(command, args, user, channel)
                return
            elif (msg.split()[0].find(self.nickname) == 0):
                command = msg.split()[1]
                args = msg.replace(self.nickname, '').split()[2:]
                self.select_command(command, args, user, channel)
                return
            # if not, try to match the message to the answer.
            else:
                if msg.lower().strip() == self._answer.answer.lower():
                    self._winner(user, channel)
                    self._save_game()
        except:
            return

    def _winner(self, user, channel):
        '''
        Congratulates the winner for guessing correctly and assigns
        points appropriately, then signals that it was guessed.
        '''
        if channel != self._game_channel:
            self.msg(channel,
                     "I'm sorry, answers must be given in the game channel.")
            return
        self._gmsg("%s GOT IT!" % user.upper())
        try:
            self._scores[user] += self._current_points
        except:
            self._scores[user] = self._current_points
        if self._current_points == 1:
            self._gmsg("%s point has been added to your score!" %
                       str(self._current_points))
        else:
            self._gmsg("%s points have been added to your score!" %
                       str(self._current_points))
        self._clue_number = 0
        self._get_new_question()

    def ctcpQuery(self, user, channel, msg):
        '''
        Responds to ctcp requests.
        Currently just reports them.
        '''
        print("CTCP recieved: "+user+":"+channel+": "+msg[0][0]+" "+msg[0][1])

    def _help(self, args, user, channel):
        '''
        Tells people how to use the bot.
        Replies differently if you are an admin or a regular user.
        Only responds to the user since there could be a game in
        progress.
        '''
        try:
            self._admins.index(user)
        except:
            self._cmsg(user, "I'm nameless's trivia bot.")
            self._cmsg(user, "Commands: score, standings, giveclue, help, "
                       "next, source")
            return
        self._cmsg(user, "I'm nameless's trivia bot.")
        self._cmsg(user, "Commands: score, standings, giveclue, help, next, "
                   "skip, source")
        self._cmsg("Admin commands: die, set <user> <score>, start, stop, "
                   "save")

    def _show_source(self, args, user, channel):
        '''
        Tells people how to use the bot.
        Only responds to the user since there could be a game in
        progress.
        '''
        self._cmsg(user, 'My source can be found at: '
                   'https://github.com/rawsonj/triviabot')

    def select_command(self, command, args, user, channel):
        '''
        Callback that responds to commands given to the bot.

        Need to differentiate between priviledged users and regular
        users.
        '''
        # set up command dicts.
        unpriviledged_commands = {'score': self._score,
                                  'help': self._help,
                                  'source': self._show_source,
                                  'standings': self._standings,
                                  'giveclue': self._give_clue,
                                  'next': self._next_vote,
                                  'skip': self._next_question
                                  }
        priviledged_commands = {'die': self._die,
                                'set': self._set_user_score,
                                'start': self._start,
                                'stop': self._stop,
                                'save': self._save_game,
                                }
        print(command, args, user, channel)
        try:
            self._admins.index(user)
            is_admin = True
        except:
            is_admin = False

        # the following takes care of sorting out functions and
        # priviledges.
        if not is_admin and command in priviledged_commands.keys():
            self.msg(channel, "%s: You don't tell me what to do." % user)
            return
        elif is_admin and command in priviledged_commands.keys():
            priviledged_commands[command](args, user, channel)
        elif command in unpriviledged_commands.keys():
            unpriviledged_commands[command](args, user, channel)
        else:
            self.describe(channel, '%slooks at %s oddly.' %
                          (config.COLOR_CODE, user))

    def _next_vote(self, args, user, channel):
        '''Implements user voting for the next question.

        Need to keep track of who voted, and how many votes.

        '''
        if not self._lc.running:
            self._gmsg("We aren't playing right now.")
            return
        try:
            self._voters.index(user)
            self._gmsg("You already voted, %s, give someone else a chance to "
                       "hate this question" % user)
            return
        except:
            if self._votes < 2:
                self._votes += 1
                self._voters.append(user)
                print(self._voters)
                self._gmsg("%s, you have voted. %s more votes needed to "
                           "skip." % (user, str(3-self._votes)))
            else:
                self._votes = 0
                self._voters = []
                self._next_question(None, None, None)

    def _start(self, args, user, channel):
        '''
        Starts the trivia game.

        TODO: Load scores from last game, if any.
        '''
        if self._lc.running:
            return
        else:
            self._lc.start(config.WAIT_INTERVAL)
            self.factory.running = True

    def _stop(self, *args):
        '''
        Stops the game and thanks people for playing,
        then saves the scores.
        '''
        if not self._lc.running:
            return
        else:
            self._lc.stop()
            self._gmsg('Thanks for playing trivia!')
            self._gmsg('Current rankings were:')
            self._standings(None, self._game_channel, None)
            self._gmsg('''Scores have been saved, and see you next game!''')
            self._save_game()
            self.factory.running = False

    def _save_game(self, *args):
        '''
        Saves the game to the data directory.
        '''
        if not path.exists(config.SAVE_DIR):
            makedirs(config.SAVE_DIR)
        with open(config.SAVE_DIR+'scores.json', 'w') as savefile:
            json.dump(self._scores, savefile)
            print("Scores have been saved.")

    def _load_game(self):
        '''
        Loads the running data from previous games.
        '''
        # ensure initialization
        self._scores = {}
        if not path.exists(config.SAVE_DIR):
            print("Save directory doesn't exist.")
            return
        try:
            with open(config.SAVE_DIR+'scores.json', 'r') as savefile:
                temp_dict = json.load(savefile)
        except:
            print("Save file doesn't exist.")
            return
        for name in temp_dict.keys():
            self._scores[str(name)] = int(temp_dict[name])
        print(self._scores)
        print("Scores loaded.")

    def _set_user_score(self, args, user, channel):
        '''
        Administrative action taken to adjust scores, if needed.
        '''
        try:
            self._scores[args[0]] = int(args[1])
        except:
            self._cmsg(user, args[0]+" not in scores database.")
            return
        self._cmsg(user, args[0]+" score set to "+args[1])

    def _die(self, *args):
        '''
        Terminates execution of the bot.
        Need to dig into twisted to figure out how this happens.
        '''
        global reactor
        self.quit(message='This is triviabot, signing off.')
        reactor.stop()
        # figure out how to kill the bot

    def _score(self, args, user, channel):
        '''
        Tells the user their score.
        '''
        try:
            self._cmsg(user, "Your current score is: %s" %
                       str(self._scores[user]))
        except:
            self._cmsg(user, "You aren't in my database.")

    def _next_question(self, args, user, channel):
        '''
        Administratively skips the current question.
        '''
        if not self._lc.running:
            self._gmsg("We are not playing right now.")
            return
        self._gmsg("Question has been skipped. The answer was: %s" %
                   self._answer.answer)
        self._clue_number = 0
        self._lc.stop()
        self._lc.start(config.WAIT_INTERVAL)

    def _standings(self, args, user, channel):
        '''
        Tells the user the complete standings in the game.

        TODO: order them.
        '''
        self._cmsg(user, "The current trivia standings are: ")
        sorted_scores = sorted(self._scores.iteritems(), key=lambda k, v:
                               (v, k), reverse=True)
        for rank, (player, score) in enumerate(sorted_scores, start=1):
            formatted_score = "%s: %s: %s" % (rank, player, score)
            self._cmsg(user, str(formatted_score))

    def _give_clue(self, args, user, channel):
        if not self._lc.running:
            self._gmsg("we are not playing right now.")
            return
        self._cmsg(channel, "Question: ")
        self._cmsg(channel, self._question)
        self._cmsg(channel, "Clue: "+self._answer.current_clue())

    def _get_new_question(self):
        '''
        Selects a new question from the questions directory and
        sets it.
        '''
        damaged_question = True
        while damaged_question:
            # randomly select file
            filename = choice(listdir(self._questions_dir))
            fd = open(config.Q_DIR+filename)
            lines = fd.read().splitlines()
            myline = choice(lines)
            fd.close()
            try:
                self._question, temp_answer = myline.split('`')
            except ValueError:
                print("Broken question:")
                print(myline)
                continue
            self._answer.set_answer(temp_answer.strip())
            damaged_question = False
Пример #4
0
 def test_masking(self):
     answer = Answer("test")
     self.assertEqual(answer.current_clue(), "****")
Пример #5
0
 def test_masking(self):
     answer = Answer("test")
     self.assertEqual(answer.current_clue(), "****")
Пример #6
0
class triviabot(irc.IRCClient):
    """
    This is the irc bot portion of the trivia bot.

    It implements the whole program so is kinda big. The algorithm is
    implemented by a series of callbacks, initiated by an admin on the
    server.
    """

    def __init__(self):
        self._answer = Answer()
        self._question = ""
        self._scores = {}
        self._clue_number = 0
        self._admins = list(config.ADMINS)
        self._admins.append(config.OWNER)
        self._game_channel = config.GAME_CHANNEL
        self._current_points = 5
        self._questions_dir = config.Q_DIR
        self._lc = LoopingCall(self._play_game)
        self._quit = False
        self._restarting = False
        self._load_game()
        self._votes = 0
        self._voters = []

    def _get_nickname(self):
        return self.factory.nickname

    nickname = property(_get_nickname)

    def _get_lineRate(self):
        return self.factory.lineRate

    lineRate = property(_get_lineRate)

    def _cmsg(self, dest, msg):
        """
        Write a colorized message.
        """

        self.msg(dest, "{}{}".format(config.COLOR_CODE, msg))

    def _gmsg(self, msg):
        """
        Write a message to the channel playing the trivia game.
        """

        self._cmsg(self._game_channel, msg)

    def _play_game(self):
        """
        Implements the main loop of the game.
        """
        points = {0: 5, 1: 3, 2: 2, 3: 1}
        if self._clue_number == 0:
            self._votes = 0
            self._voters = []
            self._get_new_question()
            self._current_points = points[self._clue_number]
            # Blank line.
            self._gmsg("")
            self._gmsg("Next question:")
            self._gmsg(self._question)
            self._gmsg("Clue: {}".format(self._answer.current_clue()))
            self._clue_number += 1
        # we must be somewhere in between
        elif self._clue_number < 4:
            self._current_points = points[self._clue_number]
            self._gmsg("Question:")
            self._gmsg(self._question)
            self._gmsg("Clue: {}".format(self._answer.give_clue()))
            self._clue_number += 1
        # no one must have gotten it.
        else:
            self._gmsg("No one got it. The answer was: {}".format(self._answer.answer))
            self._clue_number = 0
            self._get_new_question()
            # self._lc.reset()

    def signedOn(self):
        """
        Actions to perform on signon to the server.
        """
        self.join(self._game_channel)
        self.msg("NickServ", "identify {}".format(config.IDENT_STRING))
        print("Signed on as {}.".format(self.nickname))
        if self.factory.running:
            self._start(None, None, None)
        else:
            self._gmsg("Welcome to {}!".format(self._game_channel))
            self._gmsg("Have an admin start the game when you are ready.")
            self._gmsg("For how to use this bot, just say ?help or")
            self._gmsg("{} help.".format(self.nickname))

    def joined(self, channel):
        """
        Callback runs when the bot joins a channel
        """
        print("Joined {}.".format((channel)))

    def privmsg(self, user, channel, msg):
        """
        Parses out each message and initiates doing the right thing
        with it.
        """
        user, temp = user.split("!")
        print(user + " : " + channel + " : " + msg)
        # need to strip out non-printable characters if present.
        printable = string.printable
        msg = "".join(filter(lambda x: x in printable, msg))

        # parses each incoming line, and sees if it's a command for the bot.
        try:
            if msg[0] == "?":
                command = msg.replace("?", "").split()[0]
                args = msg.replace("?", "").split()[1:]
                self.select_command(command, args, user, channel)
                return
            elif msg.split()[0].find(self.nickname) == 0:
                command = msg.split()[1]
                args = msg.replace(self.nickname, "").split()[2:]
                self.select_command(command, args, user, channel)
                return
            # if not, try to match the message to the answer.
            else:
                if msg.lower().strip() == self._answer.answer.lower():
                    self._winner(user, channel)
                    self._save_game()
        except Exception as e:
            print(e)
            return

    def _winner(self, user, channel):
        """
        Congratulates the winner for guessing correctly and assigns
        points appropriately, then signals that it was guessed.
        """
        if channel != self._game_channel:
            self.msg(channel, "I'm sorry, answers must be given in the game channel.")
            return
        self._gmsg("{} GOT IT!".format(user.upper()))
        self._gmsg("""If there was any doubt, the correct answer was: {}""".format(self._answer.answer))
        try:
            self._scores[user] += self._current_points
        except:
            self._scores[user] = self._current_points
        if self._current_points == 1:
            self._gmsg("{} point has been added to your score!".format(str(self._current_points)))
        else:
            self._gmsg("{} points have been added to your score!".format(str(self._current_points)))
        self._clue_number = 0
        self._get_new_question()

    def ctcpQuery(self, user, channel, msg):
        """
        Responds to ctcp requests.
        Currently just reports them.
        """
        print("CTCP recieved: " + user + ":" + channel + ": " + msg[0][0] + " " + msg[0][1])

    def _help(self, args, user, channel):
        """
        Tells people how to use the bot.
        Replies differently if you are an admin or a regular user.
        Only responds to the user since there could be a game in
        progress.
        """
        try:
            self._admins.index(user)
        except:
            self._cmsg(user, "I'm {}'s trivia bot.".format(config.OWNER))
            self._cmsg(user, "Commands: score, standings, giveclue, help, " "next, source")
            return
        self._cmsg(user, "I'm {}'s trivia bot.".format(config.OWNER))
        self._cmsg(user, "Commands: score, standings, giveclue, help, next, " "skip, source")
        self._cmsg(user, "Admin commands: die, set <user> <score>, start, stop, " "save")

    def _show_source(self, args, user, channel):
        """
        Tells people how to use the bot.
        Only responds to the user since there could be a game in
        progress.
        """
        self._cmsg(user, "My source can be found at: " "https://github.com/rawsonj/triviabot")

    def select_command(self, command, args, user, channel):
        """
        Callback that responds to commands given to the bot.

        Need to differentiate between priviledged users and regular
        users.
        """
        # set up command dicts.
        unpriviledged_commands = {
            "score": self._score,
            "help": self._help,
            "source": self._show_source,
            "standings": self._standings,
            "giveclue": self._give_clue,
            "next": self._next_vote,
            "skip": self._next_question,
        }
        priviledged_commands = {
            "die": self._die,
            "restart": self._restart,
            "set": self._set_user_score,
            "start": self._start,
            "stop": self._stop,
            "save": self._save_game,
        }
        print(command, args, user, channel)
        try:
            self._admins.index(user)
            is_admin = True
        except:
            is_admin = False

        # the following takes care of sorting out functions and
        # priviledges.
        if not is_admin and command in priviledged_commands:
            self.msg(channel, "{}: You don't tell me what to do.".format(user))
            return
        elif is_admin and command in priviledged_commands:
            priviledged_commands[command](args, user, channel)
        elif command in unpriviledged_commands:
            unpriviledged_commands[command](args, user, channel)
        else:
            self.describe(channel, "{}looks at {} oddly.".format(config.COLOR_CODE, user))

    def _next_vote(self, args, user, channel):
        """Implements user voting for the next question.

        Need to keep track of who voted, and how many votes.

        """
        if not self._lc.running:
            self._gmsg("We aren't playing right now.")
            return
        try:
            self._voters.index(user)
            self._gmsg("You already voted, {}, give someone else a chance to " "hate this question".format(user))
            return
        except:
            if self._votes < 2:
                self._votes += 1
                self._voters.append(user)
                print(self._voters)
                self._gmsg("{}, you have voted. {} more votes needed to " "skip.".format(user, str(3 - self._votes)))
            else:
                self._votes = 0
                self._voters = []
                self._next_question(None, None, None)

    def _start(self, args, user, channel):
        """
        Starts the trivia game.

        TODO: Load scores from last game, if any.
        """
        if self._lc.running:
            return
        else:
            self._lc.start(config.WAIT_INTERVAL)
            self.factory.running = True

    def _stop(self, *args):
        """
        Stops the game and thanks people for playing,
        then saves the scores.
        """
        if not self._lc.running:
            return
        else:
            self._lc.stop()
            self._gmsg("Thanks for playing trivia!")
            self._gmsg("Current rankings were:")
            self._standings(None, self._game_channel, None)
            self._gmsg("""Scores have been saved, and see you next game!""")
            self._save_game()
            self.factory.running = False

    def _save_game(self, *args):
        """
        Saves the game to the data directory.
        """
        with open(os.path.join(config.SAVE_DIR, "scores.json"), "w") as savefile:
            json.dump(self._scores, savefile)
            print("Scores have been saved.")

    def _load_game(self):
        """
        Loads the running data from previous games.
        """
        # ensure initialization
        self._scores = {}
        if not path.exists(config.SAVE_DIR):
            print("Save directory doesn't exist.")
            return
        try:
            with open(os.path.join(config.SAVE_DIR, "scores.json"), "r") as savefile:
                temp_dict = json.load(savefile)
        except:
            print("Save file doesn't exist.")
            return
        for name in temp_dict.keys():
            self._scores[str(name)] = int(temp_dict[name])
        print(self._scores)
        print("Scores loaded.")

    def _set_user_score(self, args, user, channel):
        """
        Administrative action taken to adjust scores, if needed.
        """
        try:
            self._scores[args[0]] = int(args[1])
        except:
            self._cmsg(user, args[0] + " not in scores database.")
            return
        self._cmsg(user, args[0] + " score set to " + args[1])

    def _die(self, *args):
        """
        Terminates execution of the bot.
        """
        self._quit = True
        self.quit(message="This is triviabot, signing off.")

    def _restart(self, *args):
        """
        Restarts the bot
        """
        self._restarting = True
        print("Restarting")
        self.quit(message="Triviabot restarting.")

    def connectionLost(self, reason):
        """
        Called when connection is lost
        """
        global reactor
        if self._restarting:
            try:
                execl(sys.executable, *([sys.executable] + sys.argv))
            except Exception as e:
                print("Failed to restart: {}".format(e))
        if self._quit:
            reactor.stop()

    def _score(self, args, user, channel):
        """
        Tells the user their score.
        """
        try:
            self._cmsg(user, "Your current score is: {}".format(str(self._scores[user])))
        except:
            self._cmsg(user, "You aren't in my database.")

    def _next_question(self, args, user, channel):
        """
        Administratively skips the current question.
        """
        if not self._lc.running:
            self._gmsg("We are not playing right now.")
            return
        self._gmsg("Question has been skipped. The answer was: {}".format(self._answer.answer))
        self._clue_number = 0
        self._lc.stop()
        self._lc.start(config.WAIT_INTERVAL)

    def _standings(self, args, user, channel):
        """
        Tells the user the complete standings in the game.

        TODO: order them.
        """
        self._cmsg(user, "The current trivia standings are: ")
        sorted_scores = sorted(self._scores.iteritems(), key=lambda (k, v): (v, k), reverse=True)
        for rank, (player, score) in enumerate(sorted_scores, start=1):
            formatted_score = "{}: {}: {}".format(rank, player, score)
            self._cmsg(user, formatted_score)

    def _give_clue(self, args, user, channel):
        if not self._lc.running:
            self._gmsg("we are not playing right now.")
            return
        self._cmsg(channel, "Question: ")
        self._cmsg(channel, self._question)
        self._cmsg(channel, "Clue: " + self._answer.current_clue())

    def _get_new_question(self):
        """
        Selects a new question from the questions directory and
        sets it.
        """
        damaged_question = True
        while damaged_question:
            # randomly select file
            filename = choice(listdir(self._questions_dir))
            fd = open(os.path.join(config.Q_DIR, filename))
            lines = fd.read().splitlines()
            myline = choice(lines)
            fd.close()
            try:
                self._question, temp_answer = myline.split("`")
            except ValueError:
                print("Broken question:")
                print(myline)
                continue
            self._answer.set_answer(temp_answer.strip())
            damaged_question = False