### # Copyright (c) 2010, quantumlemur # Copyright (c) 2011, Valentin Lorentz # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### import re import os import time import math import string import random import supybot.utils as utils import supybot.ircdb as ircdb from supybot.commands import * import supybot.plugins as plugins import supybot.ircmsgs as ircmsgs import supybot.ircutils as ircutils import supybot.schedule as schedule import supybot.callbacks as callbacks from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('Trivia') class Trivia(callbacks.Plugin): """Add the help for "@plugin help Trivia" here This should describe *how* to use this plugin.""" threaded = True def __init__(self, irc): self.__parent = super(Trivia, self) self.__parent.__init__(irc) self.games = {} self.scores = {} questionfile = self.registryValue('questionFile') if not os.path.exists(questionfile): f = open(questionfile, 'w') f.write(('If you\'re seeing this question, it means that the ' 'questions file that you specified wasn\'t found, and ' 'a new one has been created. Go get some questions!%s' 'No questions found') % self.registryValue('questionFileSeparator')) f.close() self.scorefile = self.registryValue('scoreFile') if not os.path.exists(self.scorefile): f = open(self.scorefile, 'w') f.close() f = open(self.scorefile, 'r') line = f.readline() while line: (name, score) = line.split(' ') self.scores[name] = int(score.strip('\r\n')) line = f.readline() f.close() def doPrivmsg(self, irc, msg): channel = ircutils.toLower(msg.args[0]) if not irc.isChannel(channel): return if callbacks.addressed(irc.nick, msg): return if channel in self.games: self.games[channel].answer(msg) class Game: def __init__(self, irc, channel, num, plugin): self.rng = random.Random() self.rng.seed() self.registryValue = plugin.registryValue self.irc = irc self.channel = channel self.num = num self.numAsked = 0 self.hints = 0 self.games = plugin.games self.scores = plugin.scores self.scorefile = plugin.scorefile self.questionfile = self.registryValue('questionFile') self.total = num self.active = True self.questions = [] self.roundscores = {} self.unanswered = 0 f = open(self.questionfile, 'r') line = f.readline() while line: self.questions.append(line.strip('\n\r')) line = f.readline() f.close() try: schedule.removeEvent('next_%s' % self.channel) except KeyError: pass self.newquestion() def newquestion(self): inactiveShutoff = self.registryValue('inactiveShutoff', self.channel) if self.num == 0: self.active = False elif self.unanswered > inactiveShutoff and inactiveShutoff >= 0: self.reply(_('Seems like no one\'s playing any more.')) self.active = False elif len(self.questions) == 0: self.reply(_('Oops! I ran out of questions!')) self.active = False if not self.active: self.stop() return self.hints = 0 self.num -= 1 self.numAsked += 1 which = self.rng.randint(0, len(self.questions)-1) q = self.questions.pop(which) sep = self.registryValue('questionFileSeparator') self.q = q[:q.find(sep)] self.a = q[q.find(sep)+len(sep):].split(sep) color = self.registryValue('color', self.channel) self.reply(_('\x03%s#%d of %d: %s') % (color, self.numAsked, self.total, self.q)) def event(): self.timedEvent() timeout = self.registryValue('timeout', self.channel) numHints = self.registryValue('numHints', self.channel) eventTime = time.time() + timeout / (numHints + 1) if self.active: schedule.addEvent(event, eventTime, 'next_%s' % self.channel) def stop(self): self.reply(_('Trivia stopping.')) self.active = False try: schedule.removeEvent('next_%s' % self.channel) except KeyError: pass scores = self.roundscores.iteritems() sorted = [] for i in range(0, len(self.roundscores)): item = scores.next() sorted.append(item) def cmp(a, b): return b[1] - a[1] sorted.sort(cmp) max = 3 if len(sorted) < max: max = len(sorted) #self.reply('max: %d. len: %d' % (max, len(sorted))) s = _('Top finishers: ') if max > 0: recipients = [] maxp = sorted[0][1] for i in range(0, max): item = sorted[i] s = _('%s %s %s.') % (s, item[0], item[1]) self.reply(s) del self.games[self.channel] def timedEvent(self): if self.hints >= self.registryValue('numHints', self.channel): self.reply(_('No one got the answer! It was: %s') % self.a[0]) self.unanswered += 1 self.newquestion() else: self.hint() def hint(self): self.hints += 1 ans = self.a[0] hintPercentage = self.registryValue('hintPercentage', self.channel) divider = int(math.ceil(len(ans) * hintPercentage * self.hints )) if divider == len(ans): divider -= 1 show = ans[ : divider] blank = ans[divider : ] blankChar = self.registryValue('blankChar', self.channel) blank = re.sub('\w', blankChar, blank) self.reply(_('HINT: %s%s') % (show, blank)) def event(): self.timedEvent() timeout = self.registryValue('timeout', self.channel) numHints = self.registryValue('numHints', self.channel) eventTime = time.time() + timeout / (numHints + 1) if self.active: schedule.addEvent(event, eventTime, 'next_%s' % self.channel) def answer(self, msg): correct = False for ans in self.a: dist = self.DL(str.lower(msg.args[1]), str.lower(ans)) flexibility = self.registryValue('flexibility', self.channel) if dist <= len(ans) / flexibility: correct = True #if self.registryValue('debug'): # self.reply('Distance: %d' % dist) if correct: if not msg.nick in self.scores: self.scores[msg.nick] = 0 self.scores[msg.nick] += 1 if not msg.nick in self.roundscores: self.roundscores[msg.nick] = 0 self.roundscores[msg.nick] += 1 self.unanswered = 0 self.reply(_('%s got it! The full answer was: %s. Points: %d') % (msg.nick, self.a[0], self.scores[msg.nick])) schedule.removeEvent('next_%s' % self.channel) self.writeScores() self.newquestion() def reply(self, s): self.irc.queueMsg(ircmsgs.privmsg(self.channel, s)) def writeScores(self): f = open(self.scorefile, 'w') scores = self.scores.iteritems() for i in range(0, len(self.scores)): score = scores.next() f.write('%s %s\n' % (score[0], score[1])) f.close() def DL(self, seq1, seq2): oneago = None thisrow = range(1, len(seq2) + 1) + [0] for x in xrange(len(seq1)): # Python lists wrap around for negative indices, so put the # leftmost column at the *end* of the list. This matches with # the zero-indexed strings and saves extra calculation. twoago, oneago, thisrow = oneago, thisrow, [0]*len(seq2)+[x+1] for y in xrange(len(seq2)): delcost = oneago[y] + 1 addcost = thisrow[y - 1] + 1 subcost = oneago[y - 1] + (seq1[x] != seq2[y]) thisrow[y] = min(delcost, addcost, subcost) # This block deals with transpositions if x > 0 and y > 0 and seq1[x] == seq2[y - 1] and \ seq1[x-1] == seq2[y] and seq1[x] != seq2[y]: thisrow[y] = min(thisrow[y], twoago[y - 2] + 1) return thisrow[len(seq2) - 1] @internationalizeDocstring def start(self, irc, msg, args, channel, num): """[] [] Starts a game of trivia. is only necessary if the message isn't sent in the channel itself.""" if num == None: num = self.registryValue('defaultRoundLength', channel) #elif num > 100: # irc.reply('sorry, for now, you can\'t start games with more ' # 'than 100 questions :(') # num = 100 channel = ircutils.toLower(channel) if channel in self.games: if not self.games[channel].active: del self.games[channel] try: schedule.removeEvent('next_%s' % channel) except KeyError: pass irc.reply(_('Orphaned trivia game found and removed.')) else: self.games[channel].num += num self.games[channel].total += num irc.reply(_('%d questions added to active game!') % num) else: self.games[channel] = self.Game(irc, channel, num, self) irc.noReply() start = wrap(start, ['channel', optional('positiveInt')]) @internationalizeDocstring def stop(self, irc, msg, args, channel): """[] Stops a running game of trivia. is only necessary if the message isn't sent in the channel itself.""" channel = ircutils.toLower(channel) try: schedule.removeEvent('next_%s' % channel) except KeyError: irc.error(_('No trivia started')) if channel in self.games: if self.games[channel].active: self.games[channel].stop() else: del self.games[channel] irc.reply(_('Trivia stopped')) else: irc.noReply() stop = wrap(stop, ['channel']) Class = Trivia # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: