]>
Commit | Line | Data |
---|---|---|
685e346e A |
1 | # part of psm_trivia |
2 | # provides the Trivia class for channels | |
3 | # Also I hate trivia bots. | |
4 | ||
5 | import string | |
6 | from datetime import datetime, timedelta | |
7 | import task | |
8 | ||
9 | class Trivia(object): | |
10 | # state vars: | |
11 | # constructor: | |
12 | ## string cname: channel name | |
13 | ## PSModule mod: the module calling us | |
14 | ## string theme: the name of the theme table | |
15 | ## int question_number: the number of questions to ask (if 0, unlimited) | |
16 | # | |
17 | # initialized elsewhere: | |
18 | # string[] answer: the answer to the question being asked | |
19 | # int asked_questions: the amount of questions already asked | |
20 | # int hints_given: the number of hints already given; reset to 0 by ask and | |
21 | # give_hint. | |
22 | # string winner: winner of the last round | |
23 | # int streak: number of times the winner responded to a question | |
24 | # datetime started: time when we asked the question | |
25 | asked_questions = 0 | |
26 | hints_given = 0 | |
27 | winner = "" | |
28 | streak = 0 | |
29 | started = None | |
30 | ||
c59c529c A |
31 | # XXX |
32 | themes = { | |
33 | 'anime': 'Anime', | |
34 | 'default': 'default', | |
35 | 'geography': 'Geography', | |
36 | 'history': 'History', | |
37 | 'lotr-books': 'LOTR-Books', | |
38 | 'lotr-movies': 'LOTR-Movies', | |
39 | 'movies': 'Movies', | |
40 | 'naruto': 'Naruto', | |
41 | 'sciandnature': 'ScienceAndNature', | |
42 | 'simpsons': 'Simpsons', | |
43 | 'sg1qs': 'Stargate' | |
44 | } | |
45 | ||
685e346e A |
46 | def __init__(self, cname, mod, theme, qnum): |
47 | self.cname = cname | |
48 | self.module = mod | |
49 | self.theme = theme | |
50 | self.question_number = qnum | |
51 | self.dbp = mod.dbp | |
52 | ||
53 | # Not repeating so people don't have to wait until forever | |
54 | # between answered questions | |
55 | self.question_timer = task.LoopingCall(self.ask) | |
56 | self.question_timer.start(3, False) | |
57 | if qnum == 0: | |
58 | self.module.msg(self.cname, "Starting round of trivia. Questions: unlimited") | |
59 | else: | |
60 | self.module.msg(self.cname, "Starting round of trivia. Questions: %d" % qnum) | |
61 | ||
62 | def ask(self): | |
63 | '''Asks a question to the channel.''' | |
64 | if self.question_number > 0: | |
65 | if self.asked_questions >= self.question_number: | |
66 | # call parent to remove reference from dict so GC cleans us up | |
67 | self.module.stop_trivia(self.cname, False) | |
68 | return | |
69 | ||
685e346e | 70 | try: |
f4b8fd95 | 71 | query = "SELECT tq.question, tq.answer FROM `trivia_questions` AS tq JOIN (SELECT tq2.id FROM `trivia_questions` AS tq2 JOIN `trivia_themes` AS tt ON tq2.theme_id=tt.theme_id AND tt.theme_name='%s' ORDER BY rand() LIMIT 1) AS x WHERE tq.id = x.id" |
c59c529c | 72 | self.dbp.execute(query % self.themes[self.theme]) |
685e346e A |
73 | except Exception, ex: |
74 | self.module.elog.error('Unable to look up trivia question: %s' % ex) | |
75 | return | |
76 | resultset = self.dbp.fetchone() | |
77 | # If there is a *, multiple answers may be possible. Silly Trivia DB doing | |
78 | # silly things. | |
79 | self.answer = unicode(resultset[1]).split('*') | |
80 | ||
81 | self.asked_questions += 1 | |
82 | self.hints_given = 0 | |
83 | ||
84 | self.hint_timer = task.LoopingCall(self.give_hint) | |
85 | self.hint_timer.start(10, False) | |
86 | ||
87 | # SOME questions have a question mark already appended, some don't. | |
88 | # We're gonna keep compatibility here and append a question mark. | |
89 | self.module.msg(self.cname, "%d. %s?" % (self.asked_questions, resultset[0])) | |
90 | self.started = datetime.now() | |
91 | ||
92 | def give_hint(self): | |
93 | '''Gives a hint, increases the hints_given variable and sets timer for | |
94 | the next hint being given.''' | |
95 | factor = 0 | |
96 | end = 0 | |
97 | ||
98 | if self.hints_given == 0: | |
99 | factor = 0.25 | |
100 | self.hint_timer = task.LoopingCall(self.give_hint) | |
101 | self.hint_timer.start(10, False) | |
102 | elif self.hints_given == 1: | |
103 | if len(self.answer[0]) < 20: | |
104 | factor = 0.5 | |
105 | else: | |
106 | factor = 0.4 | |
107 | self.hint_timer = task.LoopingCall(self.give_hint) | |
108 | self.hint_timer.start(20, False) | |
109 | # Also show some parts of the ending if last hint | |
110 | elif self.hints_given == 2: | |
111 | if len(self.answer[0]) < 20: | |
112 | factor = 0.5 | |
113 | else: | |
114 | factor = 0.4 | |
115 | if len(self.answer[0]) >= 32: | |
116 | end = 7 | |
117 | elif len(self.answer[0]) >= 24: | |
118 | end = 5 | |
119 | elif len(self.answer[0]) >= 16: | |
120 | end = 3 | |
121 | elif len(self.answer[0]) >= 8: | |
122 | end = 2 | |
123 | elif len(self.answer[0]) >= 4: | |
124 | end = 1 | |
125 | self.hint_timer = task.LoopingCall(self.give_hint) | |
126 | self.hint_timer.start(20, False) | |
127 | elif self.hints_given == 3: | |
128 | #End the question! | |
129 | self.module.msg(self.cname, "Time's up! The answer was: " + self.answer[0]) | |
130 | self.answer = [] | |
131 | self.hints_given = 0 | |
132 | self.hint_timer = task.LoopingCall(self.ask) | |
133 | self.hint_timer.start(5, False) | |
134 | self.streak = 0 | |
135 | return | |
136 | ||
137 | # Length from the beginning of the string to be shown | |
138 | length = (int)(len(self.answer[0]) * factor) | |
139 | # substring from the beginning to length to be shown | |
140 | showstr = self.answer[0][0:length] | |
141 | # substring from the middle of the string to be hidden | |
142 | hidestr = "" | |
143 | # if "end", the number of characters to show at the end is not 0 only go until there | |
144 | if end > 0: | |
145 | hidestr = self.answer[0][length:len(self.answer[0])-end] | |
146 | else: | |
147 | hidestr = self.answer[0][length:] | |
148 | tltable = dict(zip(map(ord, u'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'), map(ord, u'**************************************************************'))) | |
149 | hidestr = hidestr.translate(tltable) | |
150 | # substring to be shown if there's an end | |
151 | endstr = "" | |
152 | if end > 0: | |
153 | endstr = self.answer[0][len(self.answer[0])-end:] | |
154 | # Hint to be sent | |
155 | hint = showstr + hidestr + endstr | |
156 | self.module.msg(self.cname, "Hint: " + hint) | |
157 | ||
158 | self.hints_given += 1 | |
159 | ||
160 | def check_answer(self, nick, answer): | |
161 | '''checks if a given answer is the one we're looking for. self.module | |
162 | will need to call this; we'll only check for validity and they must | |
163 | check if they even want to run this. | |
164 | We'll also apply points/streak, though.''' | |
165 | # Hey, smartass, don't answer before you know the question! | |
166 | if self.started == None: | |
167 | return | |
168 | ||
169 | now = datetime.now() | |
170 | tdiff = now - self.started | |
171 | tdelta = tdiff.seconds + tdiff.microseconds/1000000. | |
172 | # No human can answer in one second or less (compat with orig Trivia) | |
173 | if tdelta <= 1: | |
174 | return | |
175 | ||
176 | found = False | |
177 | for a in self.answer: | |
4f1ef484 | 178 | try: |
384e3a00 | 179 | # Issue #10: do not accept '200' when the correct answer is '20' |
180 | float(a) | |
181 | if answer == a: | |
182 | found = True | |
183 | break | |
184 | except ValueError: | |
969da989 O |
185 | try: |
186 | if a.lower() in answer.lower(): | |
187 | found = True | |
188 | break | |
189 | except: | |
190 | pass # XXX unicode | |
685e346e A |
191 | |
192 | if not found: | |
193 | return | |
194 | ||
195 | if self.winner == nick: | |
196 | self.streak += 1 | |
197 | else: | |
198 | self.winner = nick | |
199 | self.streak = 1 | |
200 | ||
201 | if self.hints_given == 0: | |
202 | points = 5 | |
203 | else: | |
204 | points = 4 - self.hints_given | |
205 | ||
206 | points *= self.streak | |
207 | ||
208 | # XXX: We're COMPLETELY ignoring RFC1459 casemapping here | |
209 | cid = self.module.get_cid(self.cname) | |
210 | try: | |
211 | self.dbp.execute('SELECT points, fastest_time, highest_streak FROM trivia_scores WHERE nick = %s AND channel = %s', | |
212 | (nick, cid)) | |
213 | except Exception, ex: | |
214 | self.module.elog.error('Unable to look up trivia scores: %s' % ex) | |
215 | return | |
216 | res = self.dbp.fetchone() | |
217 | totalpoints = 0 | |
218 | fastest_time = 61 # greater than all of the timers added | |
219 | highest_streak = 0 | |
220 | if res is not None: | |
221 | (totalpoints, fastest_time, highest_streak) = res | |
222 | ||
223 | totalpoints += points | |
224 | ||
225 | self.module.msg(self.cname, "Winner: %s; Answer: %s; Time: %.3fs; Streak: %d; Points: %d; Total: %d" | |
226 | % (nick, self.answer[0], tdelta, self.streak, points, totalpoints)) | |
227 | ||
228 | if fastest_time > tdelta: | |
229 | fastest_time = tdelta | |
230 | if self.streak > highest_streak: | |
231 | highest_streak = self.streak | |
232 | ||
233 | if res is not None: | |
234 | self.dbp.execute('UPDATE trivia_scores SET points = %s, fastest_time = %s, highest_streak = %s WHERE nick = %s AND channel = %s', | |
235 | (totalpoints, fastest_time, highest_streak, nick.lower(), cid)) | |
236 | else: | |
237 | self.dbp.execute('INSERT INTO trivia_scores(points, fastest_time, highest_streak, nick, channel) VALUES (%s, %s, %s, %s, %s)', | |
238 | (totalpoints, fastest_time, highest_streak, nick.lower(), cid)) | |
239 | ||
240 | self.question_timer.stop() | |
241 | self.hint_timer.stop() | |
242 | self.answer = [] | |
243 | self.hints_given = 0 | |
244 | self.hint_timer = task.LoopingCall(self.ask) | |
245 | self.hint_timer.start(5, False) | |
246 | ||
247 | def stop(self, forced): | |
248 | '''Stops a round of trivia. This stops all timers associated with this | |
249 | instance and sends a message to the channel that the round is done. | |
250 | If forced is True, the message "Trivia stopped." will be shown, | |
251 | otherwise the message will be "Round of trivia complete." | |
252 | The calling instance is responsible of removing any references to us, | |
253 | this includes the dict entry.''' | |
254 | msg = "" | |
255 | ||
256 | try: | |
257 | self.question_timer.stop() | |
258 | self.hint_timer.stop() | |
259 | except AttributeError: | |
260 | pass # We might be called before hint_timer's set up | |
261 | ||
262 | if forced: | |
263 | msg = "Trivia stopped." | |
264 | else: | |
265 | msg = "Round of trivia complete." | |
266 | self.module.msg(self.cname, | |
267 | msg + " '.trivia [number]' to start playing again.") | |
268 | ||
520ccd8f O |
269 | def skip(self): |
270 | '''Stops the current question and asks the next question''' | |
271 | try: | |
272 | self.question_timer.stop() | |
273 | self.hint_timer.stop() | |
274 | except AttributeError: | |
275 | return | |
276 | ||
277 | self.answer = [] | |
278 | self.hints_given = 0 | |
279 | self.hint_timer = task.LoopingCall(self.ask) | |
280 | self.hint_timer.start(5, False) | |
281 | ||
282 | self.module.msg(self.cname, "Skipping question") |