]> jfr.im git - irc/rizon/acid.git/blame - pyva/pyva/src/main/python/trivia/trivia_engine.py
Merge remote-tracking branch 'gl/milos/pyva+registrationreadconf'
[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:
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