1 # Erebus IRC bot - Author: Erebus Team
3 # This file is released into the public domain; see http://unlicense.org/
6 # stop game after X unanswered questions
8 # ability to REDUCE users points
19 'author': 'Erebus Team',
20 'license': 'public domain',
21 'compatible': [1], # compatible module API versions
22 'depends': [], # other modules required to work properly?
27 lib
= modlib
.modlib(__name__
)
28 def modstart(parent
, *args
, **kwargs
):
30 return lib
.modstart(parent
, *args
, **kwargs
)
31 def modstop(*args
, **kwargs
):
35 return lib
.modstop(*args
, **kwargs
)
38 import json
, random
, threading
, re
40 def findnth(haystack
, needle
, n
): #http://stackoverflow.com/a/1884151
41 parts
= haystack
.split(needle
, n
+1)
44 return len(haystack
)-len(parts
[-1])-len(needle
)
46 class TriviaState(object):
47 def __init__(self
, questionfile
):
48 self
.questionfile
= questionfile
49 self
.db
= json
.load(open(questionfile
, "r"))
50 self
.chan
= self
.db
['chan']
55 self
.hintanswer
= None
56 self
.revealpossibilities
= None
59 if threading
is not None and threading
._Timer
is not None and isinstance(self
.steptimer
, threading
._Timer
):
60 self
.steptimer
.cancel()
61 if json
is not None and json
.dump
is not None:
62 json
.dump(self
.db
, open(self
.questionfile
, "w"), indent
=4, separators
=(',',': '))
65 return self
.parent
.channel(self
.chan
)
67 return self
.getchan().bot
69 def nexthint(self
, hintnum
):
70 if self
.hintanswer
is None:
71 if isinstance(self
.curq
['answer'], basestring
): self
.hintanswer
= self
.curq
['answer']
72 else: self
.hintanswer
= random
.choice(self
.curq
['answer'])
73 answer
= self
.hintanswer
75 if self
.hintstr
is None or self
.revealpossibilities
is None:
76 self
.hintstr
= list(re
.sub(r
'[a-zA-Z0-9]', '*', answer
))
77 self
.revealpossibilities
= range(''.join(self
.hintstr
).count('*'))
79 reveal
= int(len(self
.hintstr
) * (7/24.0))
80 for i
in range(reveal
):
81 revealcount
= random
.choice(self
.revealpossibilities
)
82 revealloc
= findnth(''.join(self
.hintstr
), '*', revealcount
)
83 self
.revealpossibilities
.remove(revealcount
)
84 self
.hintstr
[revealloc
] = answer
[revealloc
]
85 self
.parent
.channel(self
.chan
).bot
.msg(self
.chan
, "Here's a hint: %s" % (''.join(self
.hintstr
)))
88 self
.steptimer
= threading
.Timer(HINTTIMER
, self
.nexthint
, args
=[hintnum
+1])
89 self
.steptimer
.start()
91 self
.steptimer
= threading
.Timer(HINTTIMER
, self
.nextquestion
, args
=[True])
92 self
.steptimer
.start()
94 def nextquestion(self
, qskipped
=False):
96 self
.getbot().msg(self
.getchan(), "Fail! The correct answer was: %s" % (self
.hintanswer
))
98 if isinstance(self
.steptimer
, threading
._Timer
):
99 self
.steptimer
.cancel()
101 self
.hintanswer
= None
102 self
.revealpossibilities
= None
105 if state
.nextq
is not None:
110 nextq
= random
.choice(self
.db
['questions'])
113 qtext
= "\00300,01Next up: "
114 qary
= nextq
['question'].split(None)
116 qtext
+= "\00300,01"+qword
+"\00301,01"+chr(random
.randrange(32,126))
117 self
.getbot().msg(self
.chan
, qtext
)
119 self
.steptimer
= threading
.Timer(HINTTIMER
, self
.nexthint
, args
=[1])
120 self
.steptimer
.start()
122 def checkanswer(self
, answer
):
123 if self
.curq
is None:
125 elif isinstance(self
.curq
['answer'], basestring
):
126 return answer
.lower() == self
.curq
['answer']
127 else: # assume it's a list or something.
128 return answer
.lower() in self
.curq
['answer']
130 def addpoint(self
, _user
, count
=1):
133 if user
in self
.db
['users']:
134 self
.db
['users'][user
]['points'] += count
136 self
.db
['users'][user
] = {'points': count, 'realnick': _user, 'rank': len(self.db['ranks'])}
137 self
.db
['ranks'].append(user
)
139 oldrank
= self
.db
['users'][user
]['rank']
141 nextperson
= self
.db
['ranks'][oldrank
-1]
142 if self
.db
['users'][user
]['points'] > self
.db
['users'][nextperson
]['points']:
143 self
.db
['ranks'][oldrank
-1] = user
144 self
.db
['users'][user
]['rank'] = oldrank
-1
145 self
.db
['ranks'][oldrank
] = nextperson
146 self
.db
['users'][nextperson
]['rank'] = oldrank
150 return self
.db
['users'][user
]['points']
152 def points(self
, user
):
153 user
= str(user
).lower()
154 if user
in self
.db
['users']:
155 return self
.db
['users'][user
]['points']
159 def rank(self
, user
):
160 user
= str(user
).lower()
161 if user
in self
.db
['users']:
162 return self
.db
['users'][user
]['rank']+1
164 return len(self
.db
['users'])+1
166 def targetuser(self
, user
):
167 user
= str(user
).lower()
168 if user
in self
.db
['users']:
169 rank
= self
.db
['users'][user
]['rank']
171 return "you're in the lead!"
173 return self
.db
['ranks'][rank
-1]
175 return self
.db
['ranks'][-1]
176 def targetpoints(self
, user
):
177 user
= str(user
).lower()
178 if user
in self
.db
['users']:
179 rank
= self
.db
['users'][user
]['rank']
183 return self
.db
['users'][self
.db
['ranks'][rank
-1]]['points']
185 return self
.db
['users'][self
.db
['ranks'][-1]]['points']
187 state
= TriviaState("/home/jrunyon/erebus/modules/trivia.json") #TODO get path from config
189 @lib.hookchan(state
.db
['chan'])
190 def trivia_checkanswer(bot
, user
, chan
, *args
):
191 line
= ' '.join([str(arg
) for arg
in args
])
192 if state
.checkanswer(line
):
193 bot
.msg(chan
, "\00308%s\003 has it! The answer was \00308%s\003. Current points: %d. Rank: %d. Target: %s (%s)." % (user
, line
, state
.addpoint(user
), state
.rank(user
), state
.targetuser(user
), state
.targetpoints(user
)))
197 def cmd_points(bot
, user
, chan
, realtarget
, *args
):
198 if chan
== realtarget
: replyto
= chan
201 if len(args
) != 0: who
= args
[0]
204 bot
.msg(replyto
, "%s has %d points." % (who
, state
.points(who
)))
206 @lib.hook('give', clevel
=lib
.OP
)
208 def cmd_give(bot
, user
, chan
, realtarget
, *args
):
211 numpoints
= int(args
[1])
214 balance
= state
.addpoint(whoto
, numpoints
)
217 state
.db
['ranks'].sort(key
=lambda nick
: state
.db
['users'][nick
]['points'], reverse
=True)
218 bot
.msg(chan
, "%s gave %s %d points. New balance: %d" % (user
, whoto
, numpoints
, balance
))
220 @lib.hook('setnext', clevel
=lib
.OP
)
222 def cmd_setnext(bot
, user
, chan
, realtarget
, *args
):
223 line
= ' '.join([str(arg
) for arg
in args
])
224 linepieces
= line
.split('*')
225 question
= linepieces
[0].strip()
226 answer
= linepieces
[1].strip()
227 state
.nextq
= {'question':question,'answer':answer}
228 bot
.msg(user
, "Done.")
230 @lib.hook('skip', clevel
=lib
.KNOWN
)
231 def cmd_skip(bot
, user
, chan
, realtarget
, *args
):
235 def cmd_start(bot
, user
, chan
, realtarget
, *args
):
236 if chan
== realtarget
: replyto
= chan
239 if state
.curq
is None:
242 bot
.msg(replyto
, "Game is already started!")
244 @lib.hook('stop', clevel
=lib
.KNOWN
)
245 def cmd_stop(bot
, user
, chan
, realtarget
, *args
):
247 bot
.msg(state
.chan
, "Game stopped by %s" % (user
))
249 bot
.msg(user
, "Game isn't running.")
252 if state
.curq
is not None:
254 if isinstance(state
.steptimer
, threading
._Timer
):
255 state
.steptimer
.cancel()
261 def cmd_rank(bot
, user
, chan
, realtarget
, *args
):
262 if chan
== realtarget
: replyto
= chan
265 if len(args
) != 0: who
= args
[0]
268 bot
.msg(replyto
, "%s is in %d place. Target is: %s (%d points)." % (who
, state
.rank(who
), state
.targetuser(who
), state
.targetpoints(who
)))
271 def cmd_top10(bot
, user
, chan
, realtarget
, *args
):
272 if chan
== realtarget
: replyto
= chan
276 for nick
in state
.db
['ranks'][0:10]:
277 user
= state
.db
['users'][nick
]
278 replylist
.append("%s (%d)" % (user
['realnick'], user
['points']))
279 bot
.msg(replyto
, ', '.join(replylist
))