]> jfr.im git - irc/rizon/acid.git/blob - pyva/pyva/src/main/python/trivia/trivia_engine.py
Allow channel half operators or higher to skip a trivia question
[irc/rizon/acid.git] / pyva / pyva / src / main / python / trivia / trivia_engine.py
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 # 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
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
70 try:
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"
72 self.dbp.execute(query % self.themes[self.theme])
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:
178 try:
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:
185 try:
186 if a.lower() in answer.lower():
187 found = True
188 break
189 except:
190 pass # XXX unicode
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
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")