]>
Commit | Line | Data |
---|---|---|
80d02bd8 | 1 | # Erebus IRC bot - Author: Erebus Team |
c695f740 | 2 | # trivia module |
80d02bd8 | 3 | # This file is released into the public domain; see http://unlicense.org/ |
4 | ||
c695f740 | 5 | #TODO: |
fadbf980 | 6 | # stop game after X unanswered questions |
c695f740 | 7 | # bonus points |
8 | # ability to REDUCE users points | |
9 | # dynamic questions | |
10 | # v2 | |
11 | # team play | |
12 | # statistics | |
13 | ||
fadbf980 | 14 | HINTTIMER = 2.0 |
15 | HINTNUM = 3 | |
16 | ||
80d02bd8 | 17 | # module info |
18 | modinfo = { | |
19 | 'author': 'Erebus Team', | |
20 | 'license': 'public domain', | |
21 | 'compatible': [1], # compatible module API versions | |
22 | 'depends': [], # other modules required to work properly? | |
23 | } | |
24 | ||
25 | # preamble | |
26 | import modlib | |
27 | lib = modlib.modlib(__name__) | |
28 | def modstart(parent, *args, **kwargs): | |
9557ee54 | 29 | state.parent = parent |
80d02bd8 | 30 | return lib.modstart(parent, *args, **kwargs) |
31 | def modstop(*args, **kwargs): | |
fadbf980 | 32 | stop() |
80d02bd8 | 33 | global state |
34 | del state | |
35 | return lib.modstop(*args, **kwargs) | |
36 | ||
37 | # module code | |
b16b8c05 | 38 | import json, random, threading, re |
39 | ||
40 | def findnth(haystack, needle, n): #http://stackoverflow.com/a/1884151 | |
41 | parts = haystack.split(needle, n+1) | |
42 | if len(parts)<=n+1: | |
43 | return -1 | |
44 | return len(haystack)-len(parts[-1])-len(needle) | |
80d02bd8 | 45 | |
46 | class TriviaState(object): | |
9557ee54 | 47 | def __init__(self, questionfile): |
80d02bd8 | 48 | self.questionfile = questionfile |
49 | self.db = json.load(open(questionfile, "r")) | |
9557ee54 | 50 | self.chan = self.db['chan'] |
51 | self.curq = None | |
c695f740 | 52 | self.nextq = None |
b16b8c05 | 53 | self.steptimer = None |
54 | self.hintstr = None | |
55 | self.hintanswer = None | |
56 | self.revealpossibilities = None | |
80d02bd8 | 57 | |
58 | def __del__(self): | |
b16b8c05 | 59 | if threading is not None and threading._Timer is not None and isinstance(self.steptimer, threading._Timer): |
60 | self.steptimer.cancel() | |
c695f740 | 61 | if json is not None and json.dump is not None: |
62 | json.dump(self.db, open(self.questionfile, "w"), indent=4, separators=(',',': ')) | |
80d02bd8 | 63 | |
fadbf980 | 64 | def getchan(self): |
65 | return self.parent.channel(self.chan) | |
66 | def getbot(self): | |
67 | return self.getchan().bot | |
68 | ||
b16b8c05 | 69 | def nexthint(self, hintnum): |
70 | if self.hintanswer is None: | |
71 | if isinstance(self.curq['answer'], basestring): self.hintanswer = self.curq['answer'] | |
72 | else: self.hintanswer = random.choice(self.curq['answer']) | |
73 | answer = self.hintanswer | |
74 | ||
75 | if self.hintstr is None or self.revealpossibilities is None: | |
76 | self.hintstr = list(re.sub(r'[a-zA-Z0-9]', '*', answer)) | |
77 | self.revealpossibilities = range(''.join(self.hintstr).count('*')) | |
78 | ||
79 | reveal = int(len(self.hintstr) * (7/24.0)) | |
80 | for i in range(reveal): | |
81 | revealcount = random.choice(self.revealpossibilities) | |
82 | revealloc = findnth(''.join(self.hintstr), '*', revealcount) | |
83 | self.revealpossibilities.remove(revealcount) | |
84 | self.hintstr[revealloc] = answer[revealloc] | |
85 | self.parent.channel(self.chan).bot.msg(self.chan, "Here's a hint: %s" % (''.join(self.hintstr))) | |
86 | ||
fadbf980 | 87 | if hintnum < HINTNUM: |
88 | self.steptimer = threading.Timer(HINTTIMER, self.nexthint, args=[hintnum+1]) | |
b16b8c05 | 89 | self.steptimer.start() |
90 | else: | |
fadbf980 | 91 | self.steptimer = threading.Timer(HINTTIMER, self.nextquestion, args=[True]) |
b16b8c05 | 92 | self.steptimer.start() |
93 | ||
fadbf980 | 94 | def nextquestion(self, qskipped=False): |
95 | if qskipped: | |
96 | self.getbot().msg(self.getchan(), "Fail! The correct answer was: %s" % (self.hintanswer)) | |
97 | ||
b16b8c05 | 98 | if isinstance(self.steptimer, threading._Timer): |
99 | self.steptimer.cancel() | |
100 | self.hintstr = None | |
101 | self.hintanswer = None | |
102 | self.revealpossibilities = None | |
103 | ||
104 | ||
c695f740 | 105 | if state.nextq is not None: |
106 | nextq = state.nextq | |
107 | self.curq = nextq | |
108 | state.nextq = None | |
109 | else: | |
110 | nextq = random.choice(self.db['questions']) | |
111 | self.curq = nextq | |
112 | ||
113 | qtext = "\00300,01Next up: " | |
114 | qary = nextq['question'].split(None) | |
115 | for qword in qary: | |
116 | qtext += "\00300,01"+qword+"\00301,01"+chr(random.randrange(32,126)) | |
fadbf980 | 117 | self.getbot().msg(self.chan, qtext) |
80d02bd8 | 118 | |
fadbf980 | 119 | self.steptimer = threading.Timer(HINTTIMER, self.nexthint, args=[1]) |
b16b8c05 | 120 | self.steptimer.start() |
121 | ||
80d02bd8 | 122 | def checkanswer(self, answer): |
9557ee54 | 123 | if self.curq is None: |
124 | return False | |
125 | elif isinstance(self.curq['answer'], basestring): | |
80d02bd8 | 126 | return answer.lower() == self.curq['answer'] |
127 | else: # assume it's a list or something. | |
128 | return answer.lower() in self.curq['answer'] | |
129 | ||
130 | def addpoint(self, _user, count=1): | |
9557ee54 | 131 | _user = str(_user) |
80d02bd8 | 132 | user = _user.lower() |
133 | if user in self.db['users']: | |
134 | self.db['users'][user]['points'] += count | |
135 | else: | |
136 | self.db['users'][user] = {'points': count, 'realnick': _user, 'rank': len(self.db['ranks'])} | |
9557ee54 | 137 | self.db['ranks'].append(user) |
80d02bd8 | 138 | |
139 | oldrank = self.db['users'][user]['rank'] | |
140 | while oldrank != 0: | |
141 | nextperson = self.db['ranks'][oldrank-1] | |
142 | if self.db['users'][user]['points'] > self.db['users'][nextperson]['points']: | |
143 | self.db['ranks'][oldrank-1] = user | |
c695f740 | 144 | self.db['users'][user]['rank'] = oldrank-1 |
80d02bd8 | 145 | self.db['ranks'][oldrank] = nextperson |
c695f740 | 146 | self.db['users'][nextperson]['rank'] = oldrank |
80d02bd8 | 147 | oldrank = oldrank-1 |
148 | else: | |
149 | break | |
150 | return self.db['users'][user]['points'] | |
151 | ||
152 | def points(self, user): | |
9557ee54 | 153 | user = str(user).lower() |
80d02bd8 | 154 | if user in self.db['users']: |
155 | return self.db['users'][user]['points'] | |
156 | else: | |
157 | return 0 | |
158 | ||
159 | def rank(self, user): | |
c695f740 | 160 | user = str(user).lower() |
fadbf980 | 161 | if user in self.db['users']: |
162 | return self.db['users'][user]['rank']+1 | |
163 | else: | |
164 | return len(self.db['users'])+1 | |
80d02bd8 | 165 | |
c695f740 | 166 | def targetuser(self, user): |
167 | user = str(user).lower() | |
fadbf980 | 168 | if user in self.db['users']: |
169 | rank = self.db['users'][user]['rank'] | |
170 | if rank == 0: | |
171 | return "you're in the lead!" | |
172 | else: | |
173 | return self.db['ranks'][rank-1] | |
c695f740 | 174 | else: |
fadbf980 | 175 | return self.db['ranks'][-1] |
c695f740 | 176 | def targetpoints(self, user): |
177 | user = str(user).lower() | |
fadbf980 | 178 | if user in self.db['users']: |
179 | rank = self.db['users'][user]['rank'] | |
180 | if rank == 0: | |
181 | return "N/A" | |
182 | else: | |
183 | return self.db['users'][self.db['ranks'][rank-1]]['points'] | |
c695f740 | 184 | else: |
fadbf980 | 185 | return self.db['users'][self.db['ranks'][-1]]['points'] |
80d02bd8 | 186 | |
c695f740 | 187 | state = TriviaState("/home/jrunyon/erebus/modules/trivia.json") #TODO get path from config |
9557ee54 | 188 | |
80d02bd8 | 189 | @lib.hookchan(state.db['chan']) |
190 | def trivia_checkanswer(bot, user, chan, *args): | |
80d02bd8 | 191 | line = ' '.join([str(arg) for arg in args]) |
192 | if state.checkanswer(line): | |
c695f740 | 193 | bot.msg(chan, "\00308%s\003 has it! The answer was \00308%s\003. Current points: %d. Rank: %d. Target: %s (%s)." % (user, line, state.addpoint(user), state.rank(user), state.targetuser(user), state.targetpoints(user))) |
9557ee54 | 194 | state.nextquestion() |
80d02bd8 | 195 | |
196 | @lib.hook('points') | |
197 | def cmd_points(bot, user, chan, realtarget, *args): | |
c695f740 | 198 | if chan == realtarget: replyto = chan |
80d02bd8 | 199 | else: replyto = user |
200 | ||
201 | if len(args) != 0: who = args[0] | |
202 | else: who = user | |
203 | ||
204 | bot.msg(replyto, "%s has %d points." % (who, state.points(who))) | |
205 | ||
206 | @lib.hook('give', clevel=lib.OP) | |
207 | @lib.argsGE(1) | |
208 | def cmd_give(bot, user, chan, realtarget, *args): | |
80d02bd8 | 209 | whoto = args[0] |
c695f740 | 210 | if len(args) > 1: |
211 | numpoints = int(args[1]) | |
212 | else: | |
213 | numpoints = 1 | |
214 | balance = state.addpoint(whoto, numpoints) | |
fadbf980 | 215 | |
216 | if numpoints < 0: | |
217 | state.db['ranks'].sort(key=lambda nick: state.db['users'][nick]['points'], reverse=True) | |
c695f740 | 218 | bot.msg(chan, "%s gave %s %d points. New balance: %d" % (user, whoto, numpoints, balance)) |
219 | ||
220 | @lib.hook('setnext', clevel=lib.OP) | |
221 | @lib.argsGE(1) | |
222 | def cmd_setnext(bot, user, chan, realtarget, *args): | |
223 | line = ' '.join([str(arg) for arg in args]) | |
224 | linepieces = line.split('*') | |
225 | question = linepieces[0].strip() | |
226 | answer = linepieces[1].strip() | |
227 | state.nextq = {'question':question,'answer':answer} | |
228 | bot.msg(user, "Done.") | |
229 | ||
230 | @lib.hook('skip', clevel=lib.KNOWN) | |
231 | def cmd_skip(bot, user, chan, realtarget, *args): | |
232 | state.nextquestion() | |
233 | ||
234 | @lib.hook('start') | |
235 | def cmd_start(bot, user, chan, realtarget, *args): | |
236 | if chan == realtarget: replyto = chan | |
237 | else: replyto = user | |
238 | ||
239 | if state.curq is None: | |
240 | state.nextquestion() | |
241 | else: | |
242 | bot.msg(replyto, "Game is already started!") | |
243 | ||
244 | @lib.hook('stop', clevel=lib.KNOWN) | |
245 | def cmd_stop(bot, user, chan, realtarget, *args): | |
fadbf980 | 246 | if stop(): |
247 | bot.msg(state.chan, "Game stopped by %s" % (user)) | |
248 | else: | |
249 | bot.msg(user, "Game isn't running.") | |
c695f740 | 250 | |
fadbf980 | 251 | def stop(): |
c695f740 | 252 | if state.curq is not None: |
253 | state.curq = None | |
b16b8c05 | 254 | if isinstance(state.steptimer, threading._Timer): |
255 | state.steptimer.cancel() | |
fadbf980 | 256 | return True |
c695f740 | 257 | else: |
fadbf980 | 258 | return False |
80d02bd8 | 259 | |
260 | @lib.hook('rank') | |
80d02bd8 | 261 | def cmd_rank(bot, user, chan, realtarget, *args): |
c695f740 | 262 | if chan == realtarget: replyto = chan |
80d02bd8 | 263 | else: replyto = user |
264 | ||
c695f740 | 265 | if len(args) != 0: who = args[0] |
266 | else: who = user | |
267 | ||
fadbf980 | 268 | bot.msg(replyto, "%s is in %d place. Target is: %s (%d points)." % (who, state.rank(who), state.targetuser(who), state.targetpoints(who))) |
269 | ||
270 | @lib.hook('top10') | |
271 | def cmd_top10(bot, user, chan, realtarget, *args): | |
272 | if chan == realtarget: replyto = chan | |
273 | else: replyto = user | |
274 | ||
275 | replylist = [] | |
276 | for nick in state.db['ranks'][0:10]: | |
277 | user = state.db['users'][nick] | |
278 | replylist.append("%s (%d)" % (user['realnick'], user['points'])) | |
279 | bot.msg(replyto, ', '.join(replylist)) |