]>
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 | ||
31 | def __init__(self, cname, mod, theme, qnum): | |
32 | self.cname = cname | |
33 | self.module = mod | |
34 | self.theme = theme | |
35 | self.question_number = qnum | |
36 | self.dbp = mod.dbp | |
37 | ||
38 | # Not repeating so people don't have to wait until forever | |
39 | # between answered questions | |
40 | self.question_timer = task.LoopingCall(self.ask) | |
41 | self.question_timer.start(3, False) | |
42 | if qnum == 0: | |
43 | self.module.msg(self.cname, "Starting round of trivia. Questions: unlimited") | |
44 | else: | |
45 | self.module.msg(self.cname, "Starting round of trivia. Questions: %d" % qnum) | |
46 | ||
47 | def ask(self): | |
48 | '''Asks a question to the channel.''' | |
49 | if self.question_number > 0: | |
50 | if self.asked_questions >= self.question_number: | |
51 | # call parent to remove reference from dict so GC cleans us up | |
52 | self.module.stop_trivia(self.cname, False) | |
53 | return | |
54 | ||
55 | # Silly db design is silly | |
56 | try: | |
57 | self.dbp.execute("SELECT question, answer FROM `trivia_questions_%s` ORDER BY rand() LIMIT 1" | |
58 | % self.theme) | |
59 | except Exception, ex: | |
60 | self.module.elog.error('Unable to look up trivia question: %s' % ex) | |
61 | return | |
62 | resultset = self.dbp.fetchone() | |
63 | # If there is a *, multiple answers may be possible. Silly Trivia DB doing | |
64 | # silly things. | |
65 | self.answer = unicode(resultset[1]).split('*') | |
66 | ||
67 | self.asked_questions += 1 | |
68 | self.hints_given = 0 | |
69 | ||
70 | self.hint_timer = task.LoopingCall(self.give_hint) | |
71 | self.hint_timer.start(10, False) | |
72 | ||
73 | # SOME questions have a question mark already appended, some don't. | |
74 | # We're gonna keep compatibility here and append a question mark. | |
75 | self.module.msg(self.cname, "%d. %s?" % (self.asked_questions, resultset[0])) | |
76 | self.started = datetime.now() | |
77 | ||
78 | def give_hint(self): | |
79 | '''Gives a hint, increases the hints_given variable and sets timer for | |
80 | the next hint being given.''' | |
81 | factor = 0 | |
82 | end = 0 | |
83 | ||
84 | if self.hints_given == 0: | |
85 | factor = 0.25 | |
86 | self.hint_timer = task.LoopingCall(self.give_hint) | |
87 | self.hint_timer.start(10, False) | |
88 | elif self.hints_given == 1: | |
89 | if len(self.answer[0]) < 20: | |
90 | factor = 0.5 | |
91 | else: | |
92 | factor = 0.4 | |
93 | self.hint_timer = task.LoopingCall(self.give_hint) | |
94 | self.hint_timer.start(20, False) | |
95 | # Also show some parts of the ending if last hint | |
96 | elif self.hints_given == 2: | |
97 | if len(self.answer[0]) < 20: | |
98 | factor = 0.5 | |
99 | else: | |
100 | factor = 0.4 | |
101 | if len(self.answer[0]) >= 32: | |
102 | end = 7 | |
103 | elif len(self.answer[0]) >= 24: | |
104 | end = 5 | |
105 | elif len(self.answer[0]) >= 16: | |
106 | end = 3 | |
107 | elif len(self.answer[0]) >= 8: | |
108 | end = 2 | |
109 | elif len(self.answer[0]) >= 4: | |
110 | end = 1 | |
111 | self.hint_timer = task.LoopingCall(self.give_hint) | |
112 | self.hint_timer.start(20, False) | |
113 | elif self.hints_given == 3: | |
114 | #End the question! | |
115 | self.module.msg(self.cname, "Time's up! The answer was: " + self.answer[0]) | |
116 | self.answer = [] | |
117 | self.hints_given = 0 | |
118 | self.hint_timer = task.LoopingCall(self.ask) | |
119 | self.hint_timer.start(5, False) | |
120 | self.streak = 0 | |
121 | return | |
122 | ||
123 | # Length from the beginning of the string to be shown | |
124 | length = (int)(len(self.answer[0]) * factor) | |
125 | # substring from the beginning to length to be shown | |
126 | showstr = self.answer[0][0:length] | |
127 | # substring from the middle of the string to be hidden | |
128 | hidestr = "" | |
129 | # if "end", the number of characters to show at the end is not 0 only go until there | |
130 | if end > 0: | |
131 | hidestr = self.answer[0][length:len(self.answer[0])-end] | |
132 | else: | |
133 | hidestr = self.answer[0][length:] | |
134 | tltable = dict(zip(map(ord, u'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'), map(ord, u'**************************************************************'))) | |
135 | hidestr = hidestr.translate(tltable) | |
136 | # substring to be shown if there's an end | |
137 | endstr = "" | |
138 | if end > 0: | |
139 | endstr = self.answer[0][len(self.answer[0])-end:] | |
140 | # Hint to be sent | |
141 | hint = showstr + hidestr + endstr | |
142 | self.module.msg(self.cname, "Hint: " + hint) | |
143 | ||
144 | self.hints_given += 1 | |
145 | ||
146 | def check_answer(self, nick, answer): | |
147 | '''checks if a given answer is the one we're looking for. self.module | |
148 | will need to call this; we'll only check for validity and they must | |
149 | check if they even want to run this. | |
150 | We'll also apply points/streak, though.''' | |
151 | # Hey, smartass, don't answer before you know the question! | |
152 | if self.started == None: | |
153 | return | |
154 | ||
155 | now = datetime.now() | |
156 | tdiff = now - self.started | |
157 | tdelta = tdiff.seconds + tdiff.microseconds/1000000. | |
158 | # No human can answer in one second or less (compat with orig Trivia) | |
159 | if tdelta <= 1: | |
160 | return | |
161 | ||
162 | found = False | |
163 | for a in self.answer: | |
164 | if a.lower() in answer.lower(): | |
165 | found = True | |
166 | break | |
167 | ||
168 | if not found: | |
169 | return | |
170 | ||
171 | if self.winner == nick: | |
172 | self.streak += 1 | |
173 | else: | |
174 | self.winner = nick | |
175 | self.streak = 1 | |
176 | ||
177 | if self.hints_given == 0: | |
178 | points = 5 | |
179 | else: | |
180 | points = 4 - self.hints_given | |
181 | ||
182 | points *= self.streak | |
183 | ||
184 | # XXX: We're COMPLETELY ignoring RFC1459 casemapping here | |
185 | cid = self.module.get_cid(self.cname) | |
186 | try: | |
187 | self.dbp.execute('SELECT points, fastest_time, highest_streak FROM trivia_scores WHERE nick = %s AND channel = %s', | |
188 | (nick, cid)) | |
189 | except Exception, ex: | |
190 | self.module.elog.error('Unable to look up trivia scores: %s' % ex) | |
191 | return | |
192 | res = self.dbp.fetchone() | |
193 | totalpoints = 0 | |
194 | fastest_time = 61 # greater than all of the timers added | |
195 | highest_streak = 0 | |
196 | if res is not None: | |
197 | (totalpoints, fastest_time, highest_streak) = res | |
198 | ||
199 | totalpoints += points | |
200 | ||
201 | self.module.msg(self.cname, "Winner: %s; Answer: %s; Time: %.3fs; Streak: %d; Points: %d; Total: %d" | |
202 | % (nick, self.answer[0], tdelta, self.streak, points, totalpoints)) | |
203 | ||
204 | if fastest_time > tdelta: | |
205 | fastest_time = tdelta | |
206 | if self.streak > highest_streak: | |
207 | highest_streak = self.streak | |
208 | ||
209 | if res is not None: | |
210 | self.dbp.execute('UPDATE trivia_scores SET points = %s, fastest_time = %s, highest_streak = %s WHERE nick = %s AND channel = %s', | |
211 | (totalpoints, fastest_time, highest_streak, nick.lower(), cid)) | |
212 | else: | |
213 | self.dbp.execute('INSERT INTO trivia_scores(points, fastest_time, highest_streak, nick, channel) VALUES (%s, %s, %s, %s, %s)', | |
214 | (totalpoints, fastest_time, highest_streak, nick.lower(), cid)) | |
215 | ||
216 | self.question_timer.stop() | |
217 | self.hint_timer.stop() | |
218 | self.answer = [] | |
219 | self.hints_given = 0 | |
220 | self.hint_timer = task.LoopingCall(self.ask) | |
221 | self.hint_timer.start(5, False) | |
222 | ||
223 | def stop(self, forced): | |
224 | '''Stops a round of trivia. This stops all timers associated with this | |
225 | instance and sends a message to the channel that the round is done. | |
226 | If forced is True, the message "Trivia stopped." will be shown, | |
227 | otherwise the message will be "Round of trivia complete." | |
228 | The calling instance is responsible of removing any references to us, | |
229 | this includes the dict entry.''' | |
230 | msg = "" | |
231 | ||
232 | try: | |
233 | self.question_timer.stop() | |
234 | self.hint_timer.stop() | |
235 | except AttributeError: | |
236 | pass # We might be called before hint_timer's set up | |
237 | ||
238 | if forced: | |
239 | msg = "Trivia stopped." | |
240 | else: | |
241 | msg = "Round of trivia complete." | |
242 | self.module.msg(self.cname, | |
243 | msg + " '.trivia [number]' to start playing again.") | |
244 |