def make_catalogs_or_indexes_change_ans(self, qt: str, answer: Answer, chain: TranslationChain, result: Result): tag = '目录' if qt == 'catalogs_change' else '指标' data = self._search_direct(chain) y = [len(item) for item in data] line = self.painter.paint_line(result['year'], f'{tag}个数', y, result.raw_question) answer.save_chart(line) answer.add_answer(f'该问题的回答已渲染为图像,详见:{CHART_RENDER_DIR}/{result.raw_question}.html')
def make_begin_stats_ans(self, answer: Answer, builder: AnswerBuilder, chain: TranslationChain, result: Result): data = self._search_direct(chain) builder.feed_data(data) for item, name in builder.product_data_with_name(result['index']): years = [int(year.name) for year in item] answer.add_answer(f'指标“{name.name}”最早于{min(years)}年开始统计')
def make_indexes_or_areas_trend_ans(self, qt: str, answer: Answer, builder: AnswerBuilder, chain: TranslationChain, result: Result, mark_point: bool = False): if qt == 'areas_trend': unpack = True gen = [result['area'], result['index']] else: unpack = False gen = [result['index']] # collect data = self._search_direct(chain, unpack=unpack) builder.feed_data(data) collects = [] # 根据不同单位划分数据 for item, name in builder.product_data_with_name(*gen): collect = [] units = set([n.unit for n in item if n.unit != '']) ys = builder.group_mapping_to_float(item) if builder.add_if_is_equal_or_not(sum(ys), 0, equal=False, no=f'指标“{name.subject()}”无任何值记录,无法比较'): for unit in units: tmp = [] for y, n in zip(ys, item): tmp.append(y if n.unit == unit else 0) collect.append((name.subject(), unit, tmp)) collects.append(collect) # paint if len(collects) != 0: bar = self.painter.paint_bar(result['year'], collects, title=result.raw_question, mark_point=mark_point) answer.save_chart(bar) answer.add_answer(f'该问题的回答已渲染为图像,详见:{CHART_RENDER_DIR}/{result.raw_question}.html')
def make_index_value_ans(self, answer: Answer, builder: AnswerBuilder, chain: TranslationChain, result: Result): data = self._search_direct(chain) builder.feed_data(data) for item, name in builder.product_data_with_name( result['index'], if_is_none=lambda _, na: f'无{na.subject()}数据记录' ): answer.add_answer(f'{name.subject()}为{item.val()}')
def make_catalog_status_ans(self, answer: Answer, builder: AnswerBuilder, chain: TranslationChain, result: Result): data = self._search_direct(chain) builder.feed_data(data) for item, name in builder.product_data_with_name( result['catalog'], if_is_none=lambda _, na: f'并没有关于{result["year"][0]}年{na.subject()}的描述' ): answer.add_answer(item.info)
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 make_catalog_or_index_change_ans(self, qt: str, answer: Answer, builder: AnswerBuilder, chain: TranslationChain, result: Result): data = self._search_direct(chain) tag_name = '指标' if qt == 'index_change' else '目录' set1, set2 = set([n.name for n in data[0]]), set([n.name for n in data[1]]) diff1, diff2 = set1.difference(set2), set2.difference(set1) n1, n2 = len(diff1), len(diff2) if builder.add_if_is_equal_or_not( n1, 0, equal=False, no=f'{result["year"][1]}年与{result["year"][0]}年的{tag_name}相同' ): answer.add_answer(f'{result["year"][1]}年与{result["year"][0]}年相比,未统计{n1}个{tag_name}:' + '、'.join(diff1)) if builder.add_if_is_equal_or_not( n2, 0, equal=False, no=f'{result["year"][0]}年与{result["year"][1]}年的{tag_name}相同' ): answer.add_answer(f'{result["year"][0]}年与{result["year"][1]}年相比,未统计{n2}个{tag_name}:' + '、'.join(diff2))
def make_areas_g_compare_ans(self, answer: Answer, builder: AnswerBuilder, chain: TranslationChain, result: Result): data = self._search_direct(chain) builder.feed_data(data) for item, name in builder.product_data_with_name(result['area'], result['index']): x, y = item if builder.binary_decision( x, y, not_x=f'无{result["year"][0]}年关于{name.subject()}的数据', not_y=f'无{result["year"][0]}前一年关于{name.subject()}的数据' ): res = builder.growth_calculation(y.value, x.value) if builder.add_if_is_not_none( res, to_sub=False, no=f'{result["year"][0]}年{name.subject()}的记录非数值类型,无法计算' ): answer.add_answer(f'{result["year"][0]}年的{name.subject()}为{y.val()},' f'其去年的为{x.val()},同比{sign(res, ("减少", "增长"))}{abs(res)}%')
def __init__(self): self._answer = Answer() self._question = '' self._scores = {} self._clue_number = 0 self._admins = list(ADMINS) self._game_channel = GAME_CHANNEL self._current_points = 5 self._questions_dir = Q_DIR self._lc = LoopingCall(self._play_game) self._load_game()
def __init__(self): self._answer = Answer() self._question = '' self._category = '' 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 = [] self._newQuestionMap = {}
def make_indexes_or_areas_overall_trend_ans(self, qt: str, answer: Answer, builder: AnswerBuilder, chain: TranslationChain, result: Result): if qt == 'areas_overall_trend': unpack = True gen = [result["area"], result["index"]] else: unpack = False gen = [result["index"]] data = self._search_double_direct_then_feed(chain, unpack) builder.feed_data(data) parents = {} children = {} for x, y, f, n in builder.product_data_with_feed( *gen, if_x_is_none=lambda _1, _2, _3, na: f'无关于”{na.subject()}“的记录', if_y_is_none=lambda _1, _2, _3, na: f'无关于”{na.subject()}“的父级记录' ): xs = builder.group_mapping_to_float(x) if not builder.add_if_is_not_none(xs, to_sub=False, no=f'{n.subject()}的记录非数值类型,无法比较'): return parent = f.name + n.name if qt == 'areas_overall_trend' else f.name ys = builder.group_mapping_to_float(y) if not builder.add_if_is_not_none(ys, to_sub=False, no=f'{n.subject()}的父级记录({parent})非数值类型,无法比较'): return overall = [round(i / j, 3) if j != 0 else 0 for i, j in zip(xs, ys)] # 同一个父级指标将其子孙合并 parents[parent] = ys children.setdefault((parent, x[-1].unit), []).append((n.subject(), xs, overall)) # paint if len(parents) != 0: for bar in self.painter.paint_bar_stack_with_line(result['year'], children, parents, result.raw_question): answer.save_chart(bar) answer.add_answer(f'该问题的回答已渲染为图像,详见:{CHART_RENDER_DIR}/{result.raw_question}.html')
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 test_masking(self): answer = Answer("test") self.assertEqual(answer.current_clue(), "****")
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
def make_areas_m_or_n_compare_ans(self, qt: str, answer: Answer, builder: AnswerBuilder, chain: TranslationChain, result: Result): data = self._search_direct(chain) operator = truediv if qt == 'areas_m_compare' else sub builder.feed_data(data) for (x, y), (n1, n2) in builder.product_data_with_binary( result['area'], result['index'], if_x_is_none=lambda _1, _2, na: f'无{na[0].subject()}数据记录,无法比较', if_y_is_none=lambda _1, _2, na: f'无{na[1].subject()}数据记录,无法比较' ): # 单位检查 ux, uy = x.unit or '无', y.unit or '无' if builder.add_if_is_equal_or_not(ux, uy, no=f'{n1.subject()}的单位({ux})与{n2.subject()}的单位({uy})不同,无法比较'): answer.begin_sub_answers() n1.repr, n2.repr = x.repr, y.repr answer.add_sub_answers(f'{n1.subject()}为{x.val()},{n2.subject()}为{y.val()}') res1 = builder.binary_calculation(x.value, y.value, operator) if builder.add_if_is_not_none(res1, no=f'{n1.subject()}或{n2.subject()}非数值类型,无法比较'): if qt == 'areas_m_compare': answer.add_sub_answers(f'前者是后者的{res1}倍') else: answer.add_sub_answers(f'前者比后者{sign(res1)}{abs(res1)}{ux}') if qt == 'areas_m_compare': res2 = builder.binary_calculation(y.value, x.value, truediv) if builder.add_if_is_not_none(res2, no=f'{n1.subject()}或{n2.subject()}非数值类型,无法比较'): answer.add_sub_answers(f'后者是前者的{res2}倍') answer.end_sub_answers()
def make_exist_catalog_ans(self, answer: Answer, chain: TranslationChain, result: Result): data = self._search_direct(chain) if not all(data): answer.add_answer(f'无{result["year"][0]}年的记录。') else: answer.add_answer(f'{result["year"][0]}年目录包括: ' + ','.join([item.name for item in data[0]]))
def make_index_compose_ans(self, answer: Answer, builder: AnswerBuilder, chain: TranslationChain, result: Result): data = self._search_direct(chain) builder.feed_data(data) collect = [] units = [] sub_titles = [] # for overall sqls_overall = [sql for sql in chain.iter(3)] data_overall = self._search_direct(sqls_overall) # collect for total, (item, name) in zip( data_overall, builder.product_data_with_name(result['index']) ): # 为使两可迭代对象同步迭代和collect不用做判空,此处不使用if_is_none参数 if not item: answer.add_answer(f'指标“{name.name}”没有任何组成') continue indexes, areas = [], [] for n in item: n.life_check(result['year'][0]) if n: if n.label == 'Index': indexes.append(n.name) else: areas.append(n.name) if len(indexes) == 0 and len(areas) == 0: answer.add_answer(f'指标“{name.name}”没有任何组成') continue # for indexes sqls1 = [sql.format(i) for sql in chain.iter(1) for i in indexes] data1 = self._search_direct(sqls1) # for areas sqls2 = [sql.format(name.name, a) for sql in chain.iter(2) for a in areas] data2 = self._search_direct(sqls2) # make data pairs final_data = {} for k, v in zip(indexes + areas, data1 + data2): if not v: continue if v.child_id is None: continue try: final_data.setdefault(v.child_id, []).append((k, float(v.value))) except ValueError or TypeError: answer.add_answer(f'{name.name}中{k}的记录非数值类型,无法比较') return # make other for k, v in final_data.items(): op1 = sum([x[1] for x in v]) op2 = float(total.value) if op1 < int(op2): # 舍弃一些误差,避免图中出现极小的部分 final_data[k].append(('其他', round(op2 - op1, 2))) collect.append(final_data) units.append(total.unit) sub_titles.append(f'{name.subject()}为{total.val()},其构成分为:') # paint for pie in self.painter.paint_pie(collect, units, title=result.raw_question, sub_titles=sub_titles): answer.save_chart(pie) answer.add_answer(f'该问题的回答已渲染为图像,详见:{CHART_RENDER_DIR}/{result.raw_question}.html')
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
def make_index_or_area_overall_ans(self, qt: str, answer: Answer, builder: AnswerBuilder, chain: TranslationChain, result: Result): if qt == 'index_overall': gen = [result['index']] tag = '指标' else: gen = [result['area'], result['index']] tag = '' data = self._search_double_direct_then_feed(chain) builder.feed_data(data) for x, y, f, n in builder.product_data_with_feed( *gen, if_x_is_none=lambda _1, _2, _3, na: f'无{na.subject()}的数据记录,无法比较', if_y_is_none=lambda _1, _2, _3, na: f'无{na.subject()}的父级{tag}数据记录,无法比较' ): f.life_check(result['year'][0]) if not f: answer.add_answer(f'无{n.subject()}父级{tag}数据记录,无法比较') return answer.begin_sub_answers() unit_x, unit_y = x.unit, y[0].unit if qt == 'area_overall': # 交换值域 f.area, f.name = f.name, n.name n.repr = f.repr = x.repr answer.add_sub_answers(f'{n.subject()}为{x.val()}') answer.add_sub_answers(f'其父级{tag}{f.subject()}为{y[0].val()}') if unit_x != unit_y: answer.add_sub_answers('两者单位不同,无法比较') answer.end_sub_answers() return res1 = builder.binary_calculation(x.value, y[0].value, truediv, percentage=True) if builder.add_if_is_not_none(res1, no=f'无效的{n.subject()}值类型,无法比较'): answer.add_sub_answers(f'前者占后者的{res1}%') res2 = builder.binary_calculation(y[0].value, x.value, truediv) if builder.add_if_is_not_none(res2, no=f'无效的{n.subject()}值类型,无法比较'): answer.add_sub_answers(f'后者是前者的{res2}倍') answer.end_sub_answers()
def make_indexes_or_areas_2m_or_2n_compare_ans(self, qt: str, answer: Answer, builder: AnswerBuilder, chain: TranslationChain, result: Result): # set operator if qt in ('areas_2m_compare', 'indexes_2m_compare'): operator = truediv else: operator = sub # set gen and flatten if qt in ('areas_2m_compare', 'areas_2n_compare'): gen = [result['area'], result['index']] unpack = True else: gen = [result['index']] unpack = False # code begin data = self._search_direct(chain, unpack=unpack) builder.feed_data(data) for item, name in builder.product_data_with_name(*gen): x, y = item if builder.binary_decision( x, y, not_x=f'无关于{result["year"][0]}年的{name.subject()}的记录', not_y=f'无关于{result["year"][1]}年的{name.subject()}的记录' ): answer.begin_sub_answers() res = builder.binary_calculation(x.value, y.value, operator) if builder.add_if_is_not_none(res, no=f'{name.subject()}的记录为无效的值类型,无法比较'): answer.add_sub_answers(f'{result["year"][0]}年的{name.subject()}为{x.val()}') answer.add_sub_answers(f'{result["year"][1]}年的{name.subject()}为{y.val()}') if qt in ('areas_2m_compare', 'indexes_2m_compare'): ux, uy = x.unit, y.unit # 单位为%的数值不支持此类型比较 if ux == uy == '%': answer.add_sub_answers(f'它们单位为‘%’,不支持此类型的比较') else: answer.add_sub_answers(f'前者是后者的{res}倍') else: answer.add_sub_answers(f'前者比后者{sign(res, ("减少", "增加"))}{abs(res)}{x.unit}') answer.end_sub_answers()
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
def organize(self, qt: str, chain: TranslationChain, result: Result) -> Answer: answer = Answer() builder = AnswerBuilder(answer) # 年度总体状况 if qt == 'year_status': self.make_year_status_ans(answer, chain, result) # 年度目录状况 elif qt == 'catalog_status': self.make_catalog_status_ans(answer, builder, chain, result) # 年度目录包含哪些 elif qt == 'exist_catalog': self.make_exist_catalog_ans(answer, chain, result) # 指标值 elif qt == 'index_value': self.make_index_value_ans(answer, builder, chain, result) # 指标占总比 & 地区指标占总比 elif qt in ('index_overall', 'area_overall'): self.make_index_or_area_overall_ans(qt, answer, builder, chain, result) # 指标组成 elif qt == 'index_compose': self.make_index_compose_ans(answer, builder, chain, result) # 指标倍数比较(只有两个指标) & 指标数量比较(只有两个指标) elif qt in ('indexes_m_compare', 'indexes_n_compare'): self.make_indexes_m_or_n_compare_ans(qt, answer, builder, chain, result) elif qt in ('indexes_2m_compare', 'indexes_2n_compare', 'areas_2m_compare', 'areas_2n_compare'): self.make_indexes_or_areas_2m_or_2n_compare_ans(qt, answer, builder, chain, result) # 指标值同比比较 elif qt == 'indexes_g_compare': self.make_indexes_g_compare_ans(answer, builder, chain, result) # 地区指标值 elif qt == 'area_value': self.make_area_value_ans(answer, builder, chain, result) # 地区指标占总比的变化 & 指标占总比的变化 elif qt in ('area_2_overall', 'index_2_overall'): self.make_index_or_area_2_overall_ans(qt, answer, builder, chain, result) # 地区指标倍数比较(只有两个地区) & 地区指标数量比较(只有两个地区) elif qt in ('areas_m_compare', 'areas_n_compare'): self.make_areas_m_or_n_compare_ans(qt, answer, builder, chain, result) # 地区指标值同比比较 elif qt == 'areas_g_compare': self.make_areas_g_compare_ans(answer, builder, chain, result) # 两年目录的变化 & 两年指标的变化 elif qt in ('catalog_change', 'index_change'): self.make_catalog_or_index_change_ans(qt, answer, builder, chain, result) # 多年目录的变化 & 多年指标的变化 elif qt in ('catalogs_change', 'indexes_change'): self.make_catalogs_or_indexes_change_ans(qt, answer, chain, result) # 指标值变化(多年份) elif qt in ('indexes_trend', 'areas_trend'): self.make_indexes_or_areas_trend_ans(qt, answer, builder, chain, result) # 占总指标比的变化 elif qt in ('indexes_overall_trend', 'areas_overall_trend'): self.make_indexes_or_areas_overall_trend_ans(qt, answer, builder, chain, result) # 几个年份中的最值 elif qt in ('indexes_max', 'areas_max'): self.make_indexes_or_areas_max_ans(qt, answer, builder, chain, result) # 何时开始统计此指标 elif qt == 'begin_stats': self.make_begin_stats_ans(answer, builder, chain, result) return answer
def make_index_or_area_2_overall_ans(self, qt: str, answer: Answer, builder: AnswerBuilder, chain: TranslationChain, result: Result): years = '、'.join(result['year']) # init if qt == 'area_2_overall': unpack = True gen = [result["area"], result["index"]] else: unpack = False gen = [result["index"]] # collect data data = self._search_double_direct_then_feed(chain, unpack=unpack) builder.feed_data(data) # product data for x, y, f, n in builder.product_data_with_feed( *gen, if_x_is_none=lambda _1, _2, _3, na: f'无{years}这几年{na.subject()}的数据记录,无法比较', if_y_is_none=lambda _1, _2, _3, na: f'无{years}这几年{na.subject()}的父级数据记录,无法比较' ): temp = [] # 记录两次计算的结果值 for i, year in enumerate(result["year"]): f.life_check(year) if not f: answer.add_answer(f'无{year}年{n.subject()}的父级数据记录,无法比较') continue unit_x, unit_y = x[i].unit, y[i].unit answer.begin_sub_answers() n.repr = x[i].repr answer.add_sub_answers(f'{year}年{n.subject()}为{x[i].value}{unit_x}') answer.add_sub_answers(f'其总体{f.name}{y[i].repr}为{y[i].value}{unit_y}') if unit_x != unit_y: answer.add_sub_answers('两者单位不同,无法比较') answer.end_sub_answers() continue res = builder.binary_calculation(x[i].value, y[i].value, truediv, percentage=True) if builder.add_if_is_not_none(res, no=f'无效的{n}值类型,无法比较'): answer.add_sub_answers(f'约占总体的{res}%') temp.append(res) answer.end_sub_answers() if len(temp) == 2: num = round(temp[0] - temp[1], 2) answer.add_answer(f'前者相比后者{sign(num, ("降低", "提高"))}{abs(num)}%')
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
def make_year_status_ans(self, answer: Answer, chain: TranslationChain, result: Result): data = self._search_direct(chain) answer.add_answer(f'{result["year"][0]}年,{data[0].info}')