]> jfr.im git - irc/rizon/acid.git/blame - pyva/pyva/src/main/python/trivia/trivia_engine.py
Change default anope major to 1
[irc/rizon/acid.git] / pyva / pyva / src / main / python / trivia / trivia_engine.py
CommitLineData
685e346e
A
1# part of psm_trivia
2# provides the Trivia class for channels
3# Also I hate trivia bots.
4
5import string
6from datetime import datetime, timedelta
7import task
8
9class 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:
4f1ef484 164 try:
384e3a00 165 # Issue #10: do not accept '200' when the correct answer is '20'
166 float(a)
167 if answer == a:
168 found = True
169 break
170 except ValueError:
4f1ef484
A
171 if a.lower() in answer.lower():
172 found = True
173 break
174 except:
175 pass # XXX unicode
685e346e
A
176
177 if not found:
178 return
179
180 if self.winner == nick:
181 self.streak += 1
182 else:
183 self.winner = nick
184 self.streak = 1
185
186 if self.hints_given == 0:
187 points = 5
188 else:
189 points = 4 - self.hints_given
190
191 points *= self.streak
192
193 # XXX: We're COMPLETELY ignoring RFC1459 casemapping here
194 cid = self.module.get_cid(self.cname)
195 try:
196 self.dbp.execute('SELECT points, fastest_time, highest_streak FROM trivia_scores WHERE nick = %s AND channel = %s',
197 (nick, cid))
198 except Exception, ex:
199 self.module.elog.error('Unable to look up trivia scores: %s' % ex)
200 return
201 res = self.dbp.fetchone()
202 totalpoints = 0
203 fastest_time = 61 # greater than all of the timers added
204 highest_streak = 0
205 if res is not None:
206 (totalpoints, fastest_time, highest_streak) = res
207
208 totalpoints += points
209
210 self.module.msg(self.cname, "Winner: %s; Answer: %s; Time: %.3fs; Streak: %d; Points: %d; Total: %d"
211 % (nick, self.answer[0], tdelta, self.streak, points, totalpoints))
212
213 if fastest_time > tdelta:
214 fastest_time = tdelta
215 if self.streak > highest_streak:
216 highest_streak = self.streak
217
218 if res is not None:
219 self.dbp.execute('UPDATE trivia_scores SET points = %s, fastest_time = %s, highest_streak = %s WHERE nick = %s AND channel = %s',
220 (totalpoints, fastest_time, highest_streak, nick.lower(), cid))
221 else:
222 self.dbp.execute('INSERT INTO trivia_scores(points, fastest_time, highest_streak, nick, channel) VALUES (%s, %s, %s, %s, %s)',
223 (totalpoints, fastest_time, highest_streak, nick.lower(), cid))
224
225 self.question_timer.stop()
226 self.hint_timer.stop()
227 self.answer = []
228 self.hints_given = 0
229 self.hint_timer = task.LoopingCall(self.ask)
230 self.hint_timer.start(5, False)
231
232 def stop(self, forced):
233 '''Stops a round of trivia. This stops all timers associated with this
234 instance and sends a message to the channel that the round is done.
235 If forced is True, the message "Trivia stopped." will be shown,
236 otherwise the message will be "Round of trivia complete."
237 The calling instance is responsible of removing any references to us,
238 this includes the dict entry.'''
239 msg = ""
240
241 try:
242 self.question_timer.stop()
243 self.hint_timer.stop()
244 except AttributeError:
245 pass # We might be called before hint_timer's set up
246
247 if forced:
248 msg = "Trivia stopped."
249 else:
250 msg = "Round of trivia complete."
251 self.module.msg(self.cname,
252 msg + " '.trivia [number]' to start playing again.")
253