diff --git a/DuckHunt/README.md b/DuckHunt/README.md new file mode 100644 index 0000000..0e96cad --- /dev/null +++ b/DuckHunt/README.md @@ -0,0 +1,36 @@ +Requires Python3, Limnoria. + +Python 3 and score fixes. Plugin working. + +\_o< ~ DuckHunt game for supybot ~ >o_/ +======================================= + +How to play +----------- + * Use the "start" command to start a game. + * The bot will randomly launch ducks. Whenever a duck is launched, the first person to use the "bang" command wins a point. + * Using the "bang" command when there is no duck launched costs a point. + * If a player shoots all the ducks during a hunt, it's a perfect! This player gets extra bonus points. + * The best scores for a channel are recorded and can be displayed with the "listscores" command. + * The quickest and longest shoots are also recorded and can be displayed with the "listtimes" command. + * The "launched" command tells if there is currently a duck to shoot. + +How to install +-------------- +Just place the DuckHunt plugin in the plugins directory of your supybot installation and load the module. + +How to configure +---------------- +Several per-channel configuration variables are available (look at the "channel" command to learn more on how to configure per-channel configuration variables): + * autoRestart: Does a new hunt automatically start when the previous one is over? + * ducks: Number of ducks during a hunt? + * minthrottle: The minimum amount of time before a new duck may be launched (in seconds) + * maxthrottle: The maximum amount of time before a new duck may be launched (in seconds) + * reloadTime: The time it takes to reload your rifle once you have shot (in seconds) + * kickMode: If someone shoots when there is no duck, should he be kicked from the channel? (this requires the bot to be op on the channel) + * autoFriday: Do we need to automatically launch more ducks on friday? + * missProbability: The probability to miss the duck + +Update +------ +Get latest version at : https://github.com/veggiematts/supybot-duckhunt diff --git a/DuckHunt/__init__.py b/DuckHunt/__init__.py new file mode 100644 index 0000000..6589e91 --- /dev/null +++ b/DuckHunt/__init__.py @@ -0,0 +1,67 @@ +### +# Copyright (c) 2012, Matthias Meusburger +# 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. + +### + +""" +This is a DuckHunt game for supybot +""" + +import supybot +import supybot.world as world +from imp import reload +import importlib + +# Use this for the version of this plugin. You may wish to put a CVS keyword +# in here if you're keeping the plugin in CVS or some similar system. +__version__ = "" + +# XXX Replace this with an appropriate author or supybot.Author instance. +__author__ = supybot.Author('Matthias Meusburger', 'veggiematts', '') + +# This is a dictionary mapping supybot.Author instances to lists of +# contributions. +__contributors__ = {} + +# This is a url where the most recent plugin package can be downloaded. +__url__ = 'https://github.com/veggiematts/supybot-duckhunt' + +from . import config +from . import plugin +importlib.reload(plugin) # In case we're being reloaded. +# Add more reloads here if you add third-party modules and want them to be +# reloaded when this plugin is reloaded. Don't forget to import them as well! + +if world.testing: + from . import test + +Class = plugin.Class +configure = config.configure + + +# vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: diff --git a/DuckHunt/config.py b/DuckHunt/config.py new file mode 100644 index 0000000..8260b0e --- /dev/null +++ b/DuckHunt/config.py @@ -0,0 +1,72 @@ +### +# Copyright (c) 2012, Matthias Meusburger +# 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 supybot.conf as conf +import supybot.registry as registry + +def configure(advanced): + # This will be called by supybot to configure this module. advanced is + # a bool that specifies whether the user identified himself as an advanced + # user or not. You should effect your configuration by manipulating the + # registry as appropriate. + from supybot.questions import expect, anything, something, yn + conf.registerPlugin('DuckHunt', True) + + +DuckHunt = conf.registerPlugin('DuckHunt') +# This is where your configuration variables (if any) should go. For example: +# conf.registerGlobalValue(Quote, 'someConfigVariableName', +# registry.Boolean(False, """Help for someConfigVariableName.""")) +conf.registerChannelValue(DuckHunt, 'autoRestart', + registry.Boolean(False, """Does a new hunt automatically start when the previous one is over?""")) + +conf.registerChannelValue(DuckHunt, 'ducks', + registry.Integer(5, """Number of ducks during a hunt?""")) + +conf.registerChannelValue(DuckHunt, 'minthrottle', + registry.Integer(30, """The minimum amount of time before a new duck may be launched (in seconds)""")) + +conf.registerChannelValue(DuckHunt, 'maxthrottle', + registry.Integer(300, """The maximum amount of time before a new duck may be launched (in seconds)""")) + +conf.registerChannelValue(DuckHunt, 'reloadTime', + registry.Integer(5, """The time it takes to reload your rifle once you have shot (in seconds)""")) + +conf.registerChannelValue(DuckHunt, 'missProbability', + registry.Probability(0.2, """The probability to miss the duck""")) + +conf.registerChannelValue(DuckHunt, 'kickMode', + registry.Boolean(True, """If someone shoots when there is no duck, should he be kicked from the channel? (this requires the bot to be op on the channel)""")) + +conf.registerChannelValue(DuckHunt, 'autoFriday', + registry.Boolean(True, """ Do we need to automatically launch more ducks on friday? """)) + + + +# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: diff --git a/DuckHunt/doc/DuckHunt.html b/DuckHunt/doc/DuckHunt.html new file mode 100644 index 0000000..950f756 --- /dev/null +++ b/DuckHunt/doc/DuckHunt.html @@ -0,0 +1,415 @@ + + + + + + +Documentation for the DuckHunt plugin for Supybot + + + +
+

Documentation for the DuckHunt plugin for Supybot

+ +
+

Purpose

+

This is a DuckHunt game for supybot

+
+
+

Usage

+

A DuckHunt game for supybot. Use the "start" command to start a game. The +bot will randomly launch ducks. Whenever a duck is launched, the first +person to use the "bang" command wins a point. Using the "bang" command +when there is no duck launched costs a point.

+
+
+

Commands

+
+
bang
+
Shoots the duck!
+
dayscores
+
[<channel>] Shows the score list of the day for <channel>.
+
dbg
+
This is a debug command. If debug mode is not enabled, it won't do anything
+
fridaymode
+
[<status>] Enable/disable friday mode! (there are lots of ducks on friday :))
+
launched
+
Is there a duck right now?
+
listscores
+
[<size>] [<channel>] Shows the <size>-sized score list for <channel> (or for +the current channel if no channel is given)
+
listtimes
+
[<size>] [<channel>] Shows the <size>-sized time list for <channel> (or for +the current channel if no channel is given)
+
mergescores
+
[<channel>] <nickto> <nickfrom> nickto gets the points of nickfrom and +nickfrom is removed from the scorelist
+
mergetimes
+
[<channel>] <nickto> <nickfrom> nickto gets the best time of nickfrom if +nickfrom time is better than nickto time, and nickfrom is removed from the +timelist. Also works with worst times.
+
rmscore
+
[<channel>] <nick> Remove <nick>'s score
+
rmtime
+
[<channel>] <nick> Remove <nick>'s best time
+
score
+
<nick> Shows the score for a given nick
+
start
+
Starts the hunt
+
stop
+
Stops the current hunt
+
total
+
Shows the total amount of ducks shot in <channel> (or in the current channel +if no channel is given)
+
weekscores
+
[<week>] [<nick>] [<channel>] Shows the score list of the week for <channel>. +If <nick> is provided, it will only show <nick>'s scores.
+
+
+
+

Configuration

+
+
supybot.plugins.DuckHunt.public
+

This config variable defaults to True and is not channel specific.

+

Determines whether this plugin is publicly visible.

+
+
supybot.plugins.DuckHunt.autoRestart
+

This config variable defaults to False and is channel specific.

+

Does a new hunt automatically start when the previous one is over?

+
+
supybot.plugins.DuckHunt.ducks
+

This config variable defaults to 5 and is channel specific.

+

Number of ducks during a hunt?

+
+
supybot.plugins.DuckHunt.minthrottle
+

This config variable defaults to 30 and is channel specific.

+

The minimum amount of time before a new duck may be launched (in seconds)

+
+
supybot.plugins.DuckHunt.maxthrottle
+

This config variable defaults to 300 and is channel specific.

+

The maximum amount of time before a new duck may be launched (in seconds)

+
+
supybot.plugins.DuckHunt.reloadTime
+

This config variable defaults to 5 and is channel specific.

+

The time it takes to reload your rifle once you have shot (in seconds)

+
+
supybot.plugins.DuckHunt.missProbability
+

This config variable defaults to 0.20000000000000001 and is channel specific.

+

The probability to miss the duck

+
+
supybot.plugins.DuckHunt.kickMode
+

This config variable defaults to True and is channel specific.

+

If someone shoots when there is no duck, should he be kicked from the +channel? (this requires the bot to be op on the channel)

+
+
supybot.plugins.DuckHunt.autoFriday
+

This config variable defaults to True and is channel specific.

+

Do we need to automatically launch more ducks on friday?

+
+
+
+
+ + diff --git a/DuckHunt/doc/DuckHunt.rst b/DuckHunt/doc/DuckHunt.rst new file mode 100644 index 0000000..7ea6c69 --- /dev/null +++ b/DuckHunt/doc/DuckHunt.rst @@ -0,0 +1,119 @@ +Documentation for the DuckHunt plugin for Supybot +================================================= + +Purpose +------- +This is a DuckHunt game for supybot + +Usage +----- +A DuckHunt game for supybot. Use the "start" command to start a game. The +bot will randomly launch ducks. Whenever a duck is launched, the first +person to use the "bang" command wins a point. Using the "bang" command +when there is no duck launched costs a point. + +Commands +-------- +bang + Shoots the duck! + +dayscores + [] Shows the score list of the day for . + +dbg + This is a debug command. If debug mode is not enabled, it won't do anything + +fridaymode + [] Enable/disable friday mode! (there are lots of ducks on friday :)) + +launched + Is there a duck right now? + +listscores + [] [] Shows the -sized score list for (or for + the current channel if no channel is given) + +listtimes + [] [] Shows the -sized time list for (or for + the current channel if no channel is given) + +mergescores + [] nickto gets the points of nickfrom and + nickfrom is removed from the scorelist + +mergetimes + [] nickto gets the best time of nickfrom if + nickfrom time is better than nickto time, and nickfrom is removed from the + timelist. Also works with worst times. + +rmscore + [] Remove 's score + +rmtime + [] Remove 's best time + +score + Shows the score for a given nick + +start + Starts the hunt + +stop + Stops the current hunt + +total + Shows the total amount of ducks shot in (or in the current channel + if no channel is given) + +weekscores + [] [] [] Shows the score list of the week for . + If is provided, it will only show 's scores. + +Configuration +------------- +supybot.plugins.DuckHunt.public + This config variable defaults to True and is not channel specific. + + Determines whether this plugin is publicly visible. + +supybot.plugins.DuckHunt.autoRestart + This config variable defaults to False and is channel specific. + + Does a new hunt automatically start when the previous one is over? + +supybot.plugins.DuckHunt.ducks + This config variable defaults to 5 and is channel specific. + + Number of ducks during a hunt? + +supybot.plugins.DuckHunt.minthrottle + This config variable defaults to 30 and is channel specific. + + The minimum amount of time before a new duck may be launched (in seconds) + +supybot.plugins.DuckHunt.maxthrottle + This config variable defaults to 300 and is channel specific. + + The maximum amount of time before a new duck may be launched (in seconds) + +supybot.plugins.DuckHunt.reloadTime + This config variable defaults to 5 and is channel specific. + + The time it takes to reload your rifle once you have shot (in seconds) + +supybot.plugins.DuckHunt.missProbability + This config variable defaults to 0.20000000000000001 and is channel specific. + + The probability to miss the duck + +supybot.plugins.DuckHunt.kickMode + This config variable defaults to True and is channel specific. + + If someone shoots when there is no duck, should he be kicked from the + channel? (this requires the bot to be op on the channel) + +supybot.plugins.DuckHunt.autoFriday + This config variable defaults to True and is channel specific. + + Do we need to automatically launch more ducks on friday? + diff --git a/DuckHunt/doc/DuckHunt.stx b/DuckHunt/doc/DuckHunt.stx new file mode 100644 index 0000000..9f69f11 --- /dev/null +++ b/DuckHunt/doc/DuckHunt.stx @@ -0,0 +1,146 @@ +Documentation for the DuckHunt plugin for Supybot + + Purpose + + This is a DuckHunt game for supybot + + Usage + + A DuckHunt game for supybot. Use the "start" command to start a game. + The bot will randomly launch ducks. Whenever a duck is launched, the first + person to use the "bang" command wins a point. Using the "bang" command + when there is no duck launched costs a point. + + Commands + + * bang + + Shoots the duck! + + * dayscores + + [] Shows the score list of the day for . + + * dbg + + This is a debug command. If debug mode is not enabled, it won't do + anything + + * fridaymode + + [] Enable/disable friday mode! (there are lots of ducks on friday + :)) + + * launched + + Is there a duck right now? + + * listscores + + [] [] Shows the -sized score list for (or + for the current channel if no channel is given) + + * listtimes + + [] [] Shows the -sized time list for (or + for the current channel if no channel is given) + + * mergescores + + [] nickto gets the points of nickfrom and + nickfrom is removed from the scorelist + + * mergetimes + + [] nickto gets the best time of nickfrom if + nickfrom time is better than nickto time, and nickfrom is removed from + the timelist. Also works with worst times. + + * rmscore + + [] Remove 's score + + * rmtime + + [] Remove 's best time + + * score + + Shows the score for a given nick + + * start + + Starts the hunt + + * stop + + Stops the current hunt + + * total + + Shows the total amount of ducks shot in (or in the current + channel if no channel is given) + + * weekscores + + [] [] [] Shows the score list of the week for + . If is provided, it will only show 's scores. + + Configuration + + * supybot.plugins.DuckHunt.public + + This config variable defaults to True and is not channel specific. + + Determines whether this plugin is publicly visible. + + * supybot.plugins.DuckHunt.autoRestart + + This config variable defaults to False and is channel specific. + + Does a new hunt automatically start when the previous one is over? + + * supybot.plugins.DuckHunt.ducks + + This config variable defaults to 5 and is channel specific. + + Number of ducks during a hunt? + + * supybot.plugins.DuckHunt.minthrottle + + This config variable defaults to 30 and is channel specific. + + The minimum amount of time before a new duck may be launched (in seconds) + + * supybot.plugins.DuckHunt.maxthrottle + + This config variable defaults to 300 and is channel specific. + + The maximum amount of time before a new duck may be launched (in seconds) + + * supybot.plugins.DuckHunt.reloadTime + + This config variable defaults to 5 and is channel specific. + + The time it takes to reload your rifle once you have shot (in seconds) + + * supybot.plugins.DuckHunt.missProbability + + This config variable defaults to 0.20000000000000001 and is channel + specific. + + The probability to miss the duck + + * supybot.plugins.DuckHunt.kickMode + + This config variable defaults to True and is channel specific. + + If someone shoots when there is no duck, should he be kicked from the + channel? (this requires the bot to be op on the channel) + + * supybot.plugins.DuckHunt.autoFriday + + This config variable defaults to True and is channel specific. + + Do we need to automatically launch more ducks on friday? + diff --git a/DuckHunt/plugin.py b/DuckHunt/plugin.py new file mode 100644 index 0000000..85cd3bf --- /dev/null +++ b/DuckHunt/plugin.py @@ -0,0 +1,1175 @@ +### +# Copyright (c) 2012, Matthias Meusburger +# 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. +### + +from supybot.commands import * +import supybot.plugins as plugins +import supybot.callbacks as callbacks +import supybot.schedule as schedule +import supybot.ircdb as ircdb +import supybot.ircmsgs as ircmsgs +import supybot.log as log +import supybot.conf as conf + + +import threading, random, pickle, os, time, datetime + + +class DuckHunt(callbacks.Plugin): + """ + A DuckHunt game for supybot. Use the "start" command to start a game. + The bot will randomly launch ducks. Whenever a duck is launched, the first + person to use the "bang" command wins a point. Using the "bang" command + when there is no duck launched costs a point. + """ + + threaded = True + + # Those parameters are per-channel parameters + started = {} # Has the hunt started? + duck = {} # Is there currently a duck to shoot? + shoots = {} # Number of successfull shoots in a hunt + scores = {} # Scores for the current hunt + times = {} # Elapsed time since the last duck was launched + channelscores = {} # Saved scores for the channel + toptimes = {} # Times for the current hunt + channeltimes = {} # Saved times for the channel + worsttimes = {} # Worst times for the current hunt + channelworsttimes = {} # Saved worst times for the channel + averagetime = {} # Average shooting time for the current hunt + fridayMode = {} # Are we on friday mode? (automatic) + manualFriday = {} # Are we on friday mode? (manual) + missprobability = {} # Probability to miss a duck when shooting + week = {} # Scores for the week + channelweek = {} # Saved scores for the week + leader = {} # Who is the leader for the week? + reloading = {} # Who is currently reloading? + reloadtime = {} # Time to reload after shooting (in seconds) + + # Does a duck needs to be launched? + lastSpoke = {} + minthrottle = {} + maxthrottle = {} + throttle = {} + + # Where to save scores? + fileprefix = "DuckHunt_" + path = conf.supybot.directories.data + + # Enable the 'dbg' command, which launch a duck, if true + debug = 0 + + # Other params + perfectbonus = 5 # How many extra-points are given when someones does a perfect hunt? + toplist = 5 # How many high{scores|times} are displayed by default? + dow = int(time.strftime("%u")) # Day of week + woy = int(time.strftime("%V")) # Week of year + year = time.strftime("%Y") + dayname = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'] + + + def _calc_scores(self, channel): + """ + Adds new scores and times to the already saved ones + """ + + # scores + # Adding current scores to the channel scores + for player, value in self.scores[channel].items(): + if not player in self.channelscores[channel]: + # It's a new player + self.channelscores[channel][player] = value + else: + # It's a player that already has a saved score + self.channelscores[channel][player] += value + + # times + # Adding times scores to the channel scores + for player, value in self.toptimes[channel].items(): + if not player in self.channeltimes[channel]: + # It's a new player + self.channeltimes[channel][player] = value + else: + # It's a player that already has a saved score + # And we save the time of the current hunt if it's better than it's previous time + if(self.toptimes[channel][player] < self.channeltimes[channel][player]): + self.channeltimes[channel][player] = self.toptimes[channel][player] + + # worst times + # Adding worst times scores to the channel scores + for player, value in self.worsttimes[channel].items(): + if not player in self.channelworsttimes[channel]: + # It's a new player + self.channelworsttimes[channel][player] = value + else: + # It's a player that already has a saved score + # And we save the time of the current hunt if it's worst than it's previous time + if(self.worsttimes[channel][player] > value): + self.channelworsttimes[channel][player] = value + + # week scores + for player, value in self.scores[channel].items(): + #FIXME: If the hunt starts a day and ends the day after, this will produce an error: + if not player in self.channelweek[channel][self.woy][self.dow]: + # It's a new player + self.channelweek[channel][self.woy][self.dow][player] = value + else: + # It's a player that already has a saved score + self.channelweek[channel][self.woy][self.dow][player] += value + + + + + def _write_scores(self, channel): + """ + Write scores and times to the disk + """ + + # scores + outputfile = open(self.path.dirize(self.fileprefix + channel + ".scores"), "wb") + pickle.dump(self.channelscores[channel], outputfile) + outputfile.close() + + # times + outputfile = open(self.path.dirize(self.fileprefix + channel + ".times"), "wb") + pickle.dump(self.channeltimes[channel], outputfile) + outputfile.close() + + # worst times + outputfile = open(self.path.dirize(self.fileprefix + channel + ".worsttimes"), "wb") + pickle.dump(self.channelworsttimes[channel], outputfile) + outputfile.close() + + # week scores + outputfile = open(self.path.dirize(self.fileprefix + channel + self.year + ".weekscores"), "wb") + pickle.dump(self.channelweek[channel], outputfile) + outputfile.close() + + + + + + + + def _read_scores(self, channel): + """ + Reads scores and times from disk + """ + filename = self.path.dirize(self.fileprefix + channel) + # scores + if not self.channelscores.get(channel): + if os.path.isfile(filename + ".scores"): + inputfile = open(filename + ".scores", "rb") + self.channelscores[channel] = pickle.load(inputfile) + inputfile.close() + + # times + if not self.channeltimes.get(channel): + if os.path.isfile(filename + ".times"): + inputfile = open(filename + ".times", "rb") + self.channeltimes[channel] = pickle.load(inputfile) + inputfile.close() + + # worst times + if not self.channelworsttimes.get(channel): + if os.path.isfile(filename + ".worsttimes"): + inputfile = open(filename + ".worsttimes", "rb") + self.channelworsttimes[channel] = pickle.load(inputfile) + inputfile.close() + + # week scores + if not self.channelweek.get(channel): + if os.path.isfile(filename + self.year + ".weekscores"): + inputfile = open(filename + self.year + ".weekscores", "rb") + self.channelweek[channel] = pickle.load(inputfile) + inputfile.close() + + + + def _initdayweekyear(self, channel): + self.dow = int(time.strftime("%u")) # Day of week + self.woy = int(time.strftime("%V")) # Week of year + year = time.strftime("%Y") + + # Init week scores + try: + self.channelweek[channel] + except: + self.channelweek[channel] = {} + try: + self.channelweek[channel][self.woy] + except: + self.channelweek[channel][self.woy] = {} + try: + self.channelweek[channel][self.woy][self.dow] + except: + self.channelweek[channel][self.woy][self.dow] = {} + + + + def _initthrottle(self, irc, msg, args, channel): + + self._initdayweekyear(channel) + + if not self.leader.get(channel): + self.leader[channel] = None + + # autoFriday? + if (not self.fridayMode.get(channel)): + self.fridayMode[channel] = False + + if (not self.manualFriday.get(channel)): + self.manualFriday[channel] = False + + + if self.registryValue('autoFriday', channel) == True: + if int(time.strftime("%w")) == 5 and int(time.strftime("%H")) > 8 and int(time.strftime("%H")) < 17: + self.fridayMode[channel] = True + else: + self.fridayMode[channel] = False + + # Miss probability + if self.registryValue('missProbability', channel): + self.missprobability[channel] = self.registryValue('missProbability', channel) + else: + self.missprobability[channel] = 0.2 + + # Reload time + if self.registryValue('reloadTime', channel): + self.reloadtime[channel] = self.registryValue('reloadTime', channel) + else: + self.reloadtime[channel] = 5 + + if self.fridayMode[channel] == False and self.manualFriday[channel] == False: + # Init min throttle[currentChannel] and max throttle[currentChannel] + if self.registryValue('minthrottle', channel): + self.minthrottle[channel] = self.registryValue('minthrottle', channel) + else: + self.minthrottle[channel] = 30 + + if self.registryValue('maxthrottle', channel): + self.maxthrottle[channel] = self.registryValue('maxthrottle', channel) + else: + self.maxthrottle[channel] = 300 + + else: + self.minthrottle[channel] = 3 + self.maxthrottle[channel] = 60 + + self.throttle[channel] = random.randint(self.minthrottle[channel], self.maxthrottle[channel]) + + + def starthunt(self, irc, msg, args): + """ + Starts the hunt + """ + + currentChannel = msg.args[0] + if irc.isChannel(currentChannel): + + if(self.started.get(currentChannel) == True): + irc.reply("There is already a hunt right now!") + else: + + # First of all, let's read the score if needed + self._read_scores(currentChannel) + + self._initthrottle(irc, msg, args, currentChannel) + + # Init saved scores + try: + self.channelscores[currentChannel] + except: + self.channelscores[currentChannel] = {} + + # Init saved times + try: + self.channeltimes[currentChannel] + except: + self.channeltimes[currentChannel] = {} + + # Init saved times + try: + self.channelworsttimes[currentChannel] + except: + self.channelworsttimes[currentChannel] = {} + + # Init times + self.toptimes[currentChannel] = {} + self.worsttimes[currentChannel] = {} + + # Init bangdelay + self.times[currentChannel] = False + + # Init lastSpoke + self.lastSpoke[currentChannel] = time.time() + + # Reinit current hunt scores + if self.scores.get(currentChannel): + self.scores[currentChannel] = {} + + # Reinit reloading + self.reloading[currentChannel] = {} + + # No duck launched + self.duck[currentChannel] = False + + # Hunt started + self.started[currentChannel] = True + + # Init shoots + self.shoots[currentChannel] = 0 + + # Init averagetime + self.averagetime[currentChannel] = 0; + + # Init schedule + + # First of all, stop the scheduler if it was still running + try: + schedule.removeEvent('DuckHunt_' + currentChannel) + except KeyError: + pass + + # Then restart it + def myEventCaller(): + self._launchEvent(irc, msg) + try: + schedule.addPeriodicEvent(myEventCaller, 5, 'DuckHunt_' + currentChannel, False) + except AssertionError: + pass + + irc.reply("The hunt starts now!") + else: + irc.error('You have to be on a channel') + starthunt = wrap(starthunt) + + + def _launchEvent(self, irc, msg): + currentChannel = msg.args[0] + now = time.time() + if irc.isChannel(currentChannel): + if(self.started.get(currentChannel) == True): + if (self.duck[currentChannel] == False): + if now > self.lastSpoke[currentChannel] + self.throttle[currentChannel]: + self._launch(irc, msg, '') + + + + def stophunt(self, irc, msg, args): + """ + Stops the current hunt + """ + + currentChannel = msg.args[0] + if irc.isChannel(currentChannel): + if (self.started.get(currentChannel) == True): + self._end(irc, msg, args) + + # If someone uses the stop command, + # we stop the scheduler, even if autoRestart is enabled + try: + schedule.removeEvent('DuckHunt_' + currentChannel) + except KeyError: + irc.reply('Error: the spammer wasn\'t running! This is a bug.') + except: + irc.reply('Nothing to stop: there\'s no hunt right now.') + else: + irc.error('You have to be on a channel') + stophunt = wrap(stophunt) + + def fridaymode(self, irc, msg, args, channel, status): + """ + [] + Enable/disable friday mode! (there are lots of ducks on friday :)) + """ + if irc.isChannel(channel): + + if (status == 'status'): + irc.reply('Manual friday mode for ' + channel + ' is ' + str(self.manualFriday.get(channel))); + irc.reply('Auto friday mode for ' + channel + ' is ' + str(self.fridayMode.get(channel))); + else: + if (self.manualFriday.get(channel) == None or self.manualFriday[channel] == False): + self.manualFriday[channel] = True + irc.reply("Friday mode is now enabled! Shoot alllllllllllll the ducks!") + else: + self.manualFriday[channel] = False + irc.reply("Friday mode is now disabled.") + + self._initthrottle(irc, msg, args, channel) + else: + irc.error('You have to be on a channel') + + + fridaymode = wrap(fridaymode, ['channel', 'admin', optional('anything')]) + + def launched(self, irc, msg, args): + """ + Is there a duck right now? + """ + + currentChannel = msg.args[0] + if irc.isChannel(currentChannel): + if(self.started.get(currentChannel) == True): + if(self.duck[currentChannel] == True): + irc.reply("There is currently a duck! You can shoot it with the 'bang' command") + else: + irc.reply("There is no duck right now! Wait for one to be launched!") + else: + irc.reply("There is no hunt right now! You can start a hunt with the 'start' command") + else: + irc.error('You have to be on a channel') + launched = wrap(launched) + + + + def score(self, irc, msg, args, nick): + """ + + + Shows the score for a given nick + """ + currentChannel = msg.args[0] + if irc.isChannel(currentChannel): + self._read_scores(currentChannel) + try: + self.channelscores[currentChannel] + except: + self.channelscores[currentChannel] = {} + + + try: + irc.reply(self.channelscores[currentChannel][nick]) + except: + irc.reply("There is no score for %s on %s" % (nick, currentChannel)) + else: + irc.error('You have to be on a channel') + + score = wrap(score, ['nick']) + + + + def mergescores(self, irc, msg, args, channel, nickto, nickfrom): + """ + [] + + nickto gets the points of nickfrom and nickfrom is removed from the scorelist + """ + if irc.isChannel(channel): + self._read_scores(channel) + + # Total scores + try: + self.channelscores[channel][nickto] += self.channelscores[channel][nickfrom] + del self.channelscores[channel][nickfrom] + self._write_scores(channel) + irc.reply("Total scores merged") + + except: + irc.error("Can't merge total scores") + + # Day scores + try: + self._initdayweekyear(channel) + day = self.dow + week = self.woy + + try: + self.channelweek[channel][week][day][nickto] += self.channelweek[channel][week][day][nickfrom] + except: + self.channelweek[channel][week][day][nickto] = self.channelweek[channel][week][day][nickfrom] + + del self.channelweek[channel][week][day][nickfrom] + self._write_scores(channel) + irc.reply("Day scores merged") + + except: + irc.error("Can't merge day scores") + + + + else: + irc.error('You have to be on a channel') + + + mergescores = wrap(mergescores, ['channel', 'nick', 'nick', 'admin']) + + + + def mergetimes(self, irc, msg, args, channel, nickto, nickfrom): + """ + [] + + nickto gets the best time of nickfrom if nickfrom time is better than nickto time, and nickfrom is removed from the timelist. Also works with worst times. + """ + if irc.isChannel(channel): + try: + self._read_scores(channel) + + # Merge best times + if self.channeltimes[channel][nickfrom] < self.channeltimes[channel][nickto]: + self.channeltimes[channel][nickto] = self.channeltimes[channel][nickfrom] + del self.channeltimes[channel][nickfrom] + + # Merge worst times + if self.channelworsttimes[channel][nickfrom] > self.channelworsttimes[channel][nickto]: + self.channelworsttimes[channel][nickto] = self.channelworsttimes[channel][nickfrom] + del self.channelworsttimes[channel][nickfrom] + + self._write_scores(channel) + + irc.replySuccess() + + except: + irc.replyError() + + + else: + irc.error('You have to be on a channel') + + + mergetimes = wrap(mergetimes, ['channel', 'nick', 'nick', 'admin']) + + + + def rmtime(self, irc, msg, args, channel, nick): + """ + [] + + Remove 's best time + """ + if irc.isChannel(channel): + self._read_scores(channel) + del self.channeltimes[channel][nick] + self._write_scores(channel) + irc.replySuccess() + + else: + irc.error('Are you sure ' + str(channel) + ' is a channel?') + + rmtime = wrap(rmtime, ['channel', 'nick', 'admin']) + + + + def rmscore(self, irc, msg, args, channel, nick): + """ + [] + + Remove 's score + """ + if irc.isChannel(channel): + try: + self._read_scores(channel) + del self.channelscores[channel][nick] + self._write_scores(channel) + irc.replySuccess() + + except: + irc.replyError() + + else: + irc.error('Are you sure this is a channel?') + + rmscore = wrap(rmscore, ['channel', 'nick', 'admin']) + + + + + def dayscores(self, irc, msg, args, channel): + """ + [] + + Shows the score list of the day for . + """ + + if irc.isChannel(channel): + + self._read_scores(channel) + self._initdayweekyear(channel) + day = self.dow + week = self.woy + + if self.channelweek.get(channel): + if self.channelweek[channel].get(week): + if self.channelweek[channel][week].get(day): + # Getting all scores, to get the winner of the week + msgstring = '' + scores = sorted(iter(self.channelweek[channel][week][day].items()), key=lambda k_v2:(k_v2[1],k_v2[0]), reverse=True) + for item in scores: + msgstring += "x" + item[0] + "x: "+ str(item[1]) + " | " + + if msgstring != "": + irc.reply("Scores for today: " + msgstring) + else: + irc.reply("There aren't any day scores for today yet.") + else: + irc.reply("There aren't any day scores for today yet.") + else: + irc.reply("There aren't any day scores for today yet.") + else: + irc.reply("There aren't any day scores for this channel yet.") + else: + irc.reply("Are you sure this is a channel?") + dayscores = wrap(dayscores, ['channel']) + + + + def weekscores(self, irc, msg, args, week, nick, channel): + """ + [] [] [] + + Shows the score list of the week for . If is provided, it will only show 's scores. + """ + + if irc.isChannel(channel): + + self._read_scores(channel) + weekscores = {} + + if (not week): + week = self.woy + + if self.channelweek.get(channel): + if self.channelweek[channel].get(week): + # Showing the winner for each day + if not nick: + msgstring = '' + # for each day of week + for i in (1,2,3,4,5,6,7): + if self.channelweek[channel][week].get(i): + # Getting winner of the day + winnernick, winnerscore = max(iter(self.channelweek[channel][week][i].items()), key=lambda k_v:(k_v[1],k_v[0])) + msgstring += self.dayname[i - 1] + ": x" + winnernick + "x ("+ str(winnerscore) + ") | " + + # Getting all scores, to get the winner of the week + for i, players in self.channelweek[channel][week].items(): + for player, value in players.items(): + weekscores.setdefault(player, 0) + weekscores[player] += value + + if msgstring != "": + irc.reply("Scores for week " + str(week) + ": " + msgstring) + # Who's the winner at this point? + winnernick, winnerscore = max(iter(weekscores.items()), key=lambda k_v1:(k_v1[1],k_v1[0])) + irc.reply("Leader: x%sx with %i points." % (winnernick, winnerscore)) + + else: + irc.reply("There aren't any week scores for this week yet.") + else: + # Showing the scores of + msgstring = '' + total = 0 + for i in (1,2,3,4,5,6,7): + if self.channelweek[channel][week].get(i): + if self.channelweek[channel][week][i].get(nick): + msgstring += self.dayname[i - 1] + ": "+ str(self.channelweek[channel][week][i].get(nick)) + " | " + total += self.channelweek[channel][week][i].get(nick) + + if msgstring != "": + irc.reply(nick + " scores for week " + str(self.woy) + ": " + msgstring) + irc.reply("Total: " + str(total) + " points.") + else: + irc.reply("There aren't any week scores for this nick.") + + + else: + irc.reply("There aren't any week scores for this week yet.") + else: + irc.reply("There aren't any week scores for this channel yet.") + else: + irc.reply("Are you sure this is a channel?") + weekscores = wrap(weekscores, [optional('int'), optional('nick'), 'channel']) + + + + def listscores(self, irc, msg, args, size, channel): + """ + [] [] + + Shows the -sized score list for (or for the current channel if no channel is given) + """ + + if irc.isChannel(channel): + try: + self.channelscores[channel] + except: + self.channelscores[channel] = {} + + self._read_scores(channel) + + # How many results do we display? + if (not size): + listsize = self.toplist + else: + listsize = size + + # Sort the scores (reversed: the higher the better) + scores = sorted(iter(self.channelscores[channel].items()), key=lambda k_v9:(k_v9[1],k_v9[0]), reverse=True) + del scores[listsize:] + + msgstring = "" + for item in scores: + # Why do we show the nicks as xnickx? + # Just to prevent everyone that has ever played a hunt in the channel to be pinged every time anyone asks for the score list + msgstring += "x" + item[0] + "x: "+ str(item[1]) + " | " + if msgstring != "": + irc.reply("\_o< ~ DuckHunt top-" + str(listsize) + " scores for " + channel + " ~ >o_/") + irc.reply(msgstring) + else: + irc.reply("There aren't any scores for this channel yet.") + else: + irc.reply("Are you sure this is a channel?") + listscores = wrap(listscores, [optional('int'), 'channel']) + + + def total(self, irc, msg, args, channel): + """ + Shows the total amount of ducks shot in (or in the current channel if no channel is given) + """ + + if irc.isChannel(channel): + self._read_scores(channel) + if (self.channelscores.get(channel)): + scores = self.channelscores[channel] + total = 0 + for player, value in scores.items(): + total += value + irc.reply(str(total) + " ducks have been shot in " + channel + "!") + else: + irc.reply("There are no scores for this channel yet") + + else: + irc.reply("Are you sure this is a channel?") + total = wrap(total, ['channel']) + + + def listtimes(self, irc, msg, args, size, channel): + """ + [] [] + + Shows the -sized time list for (or for the current channel if no channel is given) + """ + + if irc.isChannel(channel): + self._read_scores(channel) + + try: + self.channeltimes[channel] + except: + self.channeltimes[channel] = {} + + try: + self.channelworsttimes[channel] + except: + self.channelworsttimes[channel] = {} + + # How many results do we display? + if (not size): + listsize = self.toplist + else: + listsize = size + + # Sort the times (not reversed: the lower the better) + times = sorted(iter(self.channeltimes[channel].items()), key=lambda k_v10:(k_v10[1],k_v10[0]), reverse=False) + del times[listsize:] + + msgstring = "" + for item in times: + # Same as in listscores for the xnickx + #msgstring += "x" + item[0] + "x: "+ str(round(item[1],2)) + " | " + msgstring.join("x{0}x: {1} | ".format(item[0], str(round(item[1],2)))) + if item: + if msgstring: + irc.reply("\_o< ~ DuckHunt top-" + str(listsize) + " times for " + channel + " ~ >o_/") + irc.reply(msgstring) + else: + irc.reply("There aren't any best times for this channel yet.") + + + times = sorted(iter(self.channelworsttimes[channel].items()), key=lambda k_v11:(k_v11[1],k_v11[0]), reverse=True) + del times[listsize:] + + msgstring = "" + for item in times: + # Same as in listscores for the xnickx + #msgstring += "x" + item[0] + "x: "+ time.strftime('%H:%M:%S', time.gmtime(item[1])) + ", " + roundseconds = round(item[1]) + delta = datetime.timedelta(seconds=roundseconds) + msgstring += "x" + item[0] + "x: " + str(delta) + " | " + if msgstring != "": + irc.reply("\_o< ~ DuckHunt top-" + str(listsize) + " longest times for " + channel + " ~ >o_/") + irc.reply(msgstring) + else: + irc.reply("There aren't any longest times for this channel yet.") + + + else: + irc.reply("Are you sure this is a channel?") + listtimes = wrap(listtimes, [optional('int'), 'channel']) + + + + def dbg(self, irc, msg, args): + """ + This is a debug command. If debug mode is not enabled, it won't do anything + """ + currentChannel = msg.args[0] + if (self.debug): + if irc.isChannel(currentChannel): + self._launch(irc, msg, '') + dbg = wrap(dbg) + + + + def bang(self, irc, msg, args): + """ + Shoots the duck! + """ + currentChannel = msg.args[0] + + if irc.isChannel(currentChannel): + if(self.started.get(currentChannel) == True): + + # bangdelay: how much time between the duck was launched and this shot? + if self.times[currentChannel]: + bangdelay = time.time() - self.times[currentChannel] + else: + bangdelay = False + + + # Is the player reloading? + if (self.reloading[currentChannel].get(msg.nick) and time.time() - self.reloading[currentChannel][msg.nick] < self.reloadtime[currentChannel]): + irc.reply("%s, you are reloading... (Reloading takes %i seconds)" % (msg.nick, self.reloadtime[currentChannel])) + return 0 + + + # This player is now reloading + self.reloading[currentChannel][msg.nick] = time.time(); + + # There was a duck + if (self.duck[currentChannel] == True): + + # Did the player missed it? + if (random.random() < self.missprobability[currentChannel]): + irc.reply("%s, you missed the duck!" % (msg.nick)) + else: + + # Adds one point for the nick that shot the duck + try: + self.scores[currentChannel][msg.nick] += 1 + except: + try: + self.scores[currentChannel][msg.nick] = 1 + except: + self.scores[currentChannel] = {} + self.scores[currentChannel][msg.nick] = 1 + + irc.reply("\_x< %s: %i (%.2f seconds)" % (msg.nick, self.scores[currentChannel][msg.nick], bangdelay)) + + self.averagetime[currentChannel] += bangdelay + + # Now save the bang delay for the player (if it's quicker than it's previous bangdelay) + try: + previoustime = self.toptimes[currentChannel][msg.nick] + if(bangdelay < previoustime): + self.toptimes[currentChannel][msg.nick] = bangdelay + except: + self.toptimes[currentChannel][msg.nick] = bangdelay + + + # Now save the bang delay for the player (if it's worst than it's previous bangdelay) + try: + previoustime = self.worsttimes[currentChannel][msg.nick] + if(bangdelay > previoustime): + self.worsttimes[currentChannel][msg.nick] = bangdelay + except: + self.worsttimes[currentChannel][msg.nick] = bangdelay + + + self.duck[currentChannel] = False + + # Reset the basetime for the waiting time before the next duck + self.lastSpoke[currentChannel] = time.time() + + if self.registryValue('ducks', currentChannel): + maxShoots = self.registryValue('ducks', currentChannel) + else: + maxShoots = 10 + + # End of Hunt + if (self.shoots[currentChannel] == maxShoots): + self._end(irc, msg, args) + + # If autorestart is enabled, we restart a hunt automatically! + if self.registryValue('autoRestart', currentChannel): + # This code shouldn't be here + self.started[currentChannel] = True + self._initthrottle(irc, msg, args, currentChannel) + if self.scores.get(currentChannel): + self.scores[currentChannel] = {} + if self.reloading.get(currentChannel): + self.reloading[currentChannel] = {} + + self.averagetime[currentChannel] = 0 + + + # There was no duck or the duck has already been shot + else: + + # Removes one point for the nick that shot + try: + self.scores[currentChannel][msg.nick] -= 1 + except: + try: + self.scores[currentChannel][msg.nick] = -1 + except: + self.scores[currentChannel] = {} + self.scores[currentChannel][msg.nick] = -1 + + # Base message + message = 'There was no duck!' + + # Adding additional message if kick + if self.registryValue('kickMode', currentChannel) and irc.nick in irc.state.channels[currentChannel].ops: + message += ' You just shot yourself!' + + # Adding nick and score + message += " %s: %i" % (msg.nick, self.scores[currentChannel][msg.nick]) + + # If we were able to have a bangdelay (ie: a duck was launched before someone did bang) + if (bangdelay): + # Adding time + message += " (" + str(round(bangdelay,2)) + " seconds)" + + # If kickMode is enabled for this channel, and the bot have op capability, let's kick! + if self.registryValue('kickMode', currentChannel) and irc.nick in irc.state.channels[currentChannel].ops: + irc.queueMsg(ircmsgs.kick(currentChannel, msg.nick, message)) + else: + # Else, just say it + irc.reply(message) + + + else: + irc.reply("There is no hunt right now! You can start a hunt with the 'start' command") + else: + irc.error('You have to be on a channel') + + bang = wrap(bang) + + + def doPrivmsg(self, irc, msg): + currentChannel = msg.args[0] + if irc.isChannel(msg.args[0]): + if (msg.args[1] == '\_o< quack!'): + message = msg.nick + ", don't pretend to be me!"; + # If kickMode is enabled for this channel, and the bot have op capability, let's kick! + if self.registryValue('kickMode', currentChannel) and irc.nick in irc.state.channels[currentChannel].ops: + irc.queueMsg(ircmsgs.kick(currentChannel, msg.nick, message)) + else: + # Else, just say it + irc.reply(message) + + + + + def _end(self, irc, msg, args): + """ + End of the hunt (is called when the hunts stop "naturally" or when someone uses the !stop command) + """ + + currentChannel = msg.args[0] + + # End the hunt + self.started[currentChannel] = False + + try: + self.channelscores[currentChannel] + except: + self.channelscores[currentChannel] = {} + + + if not self.registryValue('autoRestart', currentChannel): + irc.reply("The hunt stops now!") + + # Showing scores + if (self.scores.get(currentChannel)): + + # Getting winner + winnernick, winnerscore = max(iter(self.scores.get(currentChannel).items()), key=lambda k_v12:(k_v12[1],k_v12[0])) + if self.registryValue('ducks', currentChannel): + maxShoots = self.registryValue('ducks', currentChannel) + else: + maxShoots = 10 + + # Is there a perfect? + if (winnerscore == maxShoots): + irc.reply("\o/ %s: %i ducks out of %i: perfect!!! +%i \o/" % (winnernick, winnerscore, maxShoots, self.perfectbonus)) + self.scores[currentChannel][winnernick] += self.perfectbonus + else: + # Showing scores + #irc.reply("Winner: %s with %i points" % (winnernick, winnerscore)) + #irc.reply(self.scores.get(currentChannel)) + #TODO: Better display + irc.reply(sorted(iter(self.scores.get(currentChannel).items()), key=lambda k_v4:(k_v4[1],k_v4[0]), reverse=True)) + + + + # Getting channel best time (to see if the best time of this hunt is better) + channelbestnick = None + channelbesttime = None + if self.channeltimes.get(currentChannel): + channelbestnick, channelbesttime = min(iter(self.channeltimes.get(currentChannel).items()), key=lambda k_v5:(k_v5[1],k_v5[0])) + + # Showing best time + recordmsg = '' + if (self.toptimes.get(currentChannel)): + key,value = min(iter(self.toptimes.get(currentChannel).items()), key=lambda k_v6:(k_v6[1],k_v6[0])) + if (channelbesttime and value < channelbesttime): + recordmsg = '. This is the new record for this channel! (previous record was held by ' + channelbestnick + ' with ' + str(round(channelbesttime,2)) + ' seconds)' + else: + try: + if(value < self.channeltimes[currentChannel][key]): + recordmsg = ' (this is your new record in this channel! Your previous record was ' + str(round(self.channeltimes[currentChannel][key],2)) + ')' + except: + recordmsg = '' + + irc.reply("Best time: %s with %.2f seconds%s" % (key, value, recordmsg)) + + # Getting channel worst time (to see if the worst time of this hunt is worst) + channelworstnick = None + channelworsttime = None + if self.channelworsttimes.get(currentChannel): + channelworstnick, channelworsttime = max(iter(self.channelworsttimes.get(currentChannel).items()), key=lambda k_v7:(k_v7[1],k_v7[0])) + + + # Showing worst time + recordmsg = '' + if (self.worsttimes.get(currentChannel)): + key,value = max(iter(self.worsttimes.get(currentChannel).items()), key=lambda k_v8:(k_v8[1],k_v8[0])) + if (channelworsttime and value > channelworsttime): + recordmsg = '. This is the new longest time for this channel! (previous longest time was held by ' + channelworstnick + ' with ' + str(round(channelworsttime,2)) + ' seconds)' + else: + try: + if(value > self.channelworsttimes[currentChannel][key]): + recordmsg = ' (this is your new longest time in this channel! Your previous longest time was ' + str(round(self.channelworsttimes[currentChannel][key],2)) + ')' + except: + recordmsg = '' + + # Only display worst time if something new + if (recordmsg != ''): + irc.reply("Longest time: %s with %.2f seconds%s" % (key, value, recordmsg)) + + # Showing average shooting time: + #if (self.shoots[currentChannel] > 1): + #irc.reply("Average shooting time: %.2f seconds" % ((self.averagetime[currentChannel] / self.shoots[currentChannel]))) + + # Write the scores and times to disk + self._calc_scores(currentChannel) + self._write_scores(currentChannel) + + # Did someone took the lead? + weekscores = {} + if self.channelweek.get(currentChannel): + if self.channelweek[currentChannel].get(self.woy): + msgstring = '' + # for each day of week + for i in (1,2,3,4,5,6,7): + if self.channelweek[currentChannel][self.woy].get(i): + # Getting all scores, to get the winner of the week + for i, players in self.channelweek[currentChannel][self.woy].items(): + for player, value in players.items(): + weekscores.setdefault(player, 0) + weekscores[player] += value + winnernick, winnerscore = max(iter(weekscores.items()), key=lambda k_v3:(k_v3[1],k_v3[0])) + if (winnernick != self.leader[currentChannel]): + if self.leader[currentChannel] != None: + irc.reply("%s took the lead for the week over %s with %i points." % (winnernick, self.leader[currentChannel], winnerscore)) + else: + irc.reply("%s has the lead for the week with %i points." % (winnernick, winnerscore)) + self.leader[currentChannel] = winnernick + + + + else: + irc.reply("Not a single duck was shot during this hunt!") + + # Reinit current hunt scores + if self.scores.get(currentChannel): + self.scores[currentChannel] = {} + + # Reinit current hunt times + if self.toptimes.get(currentChannel): + self.toptimes[currentChannel] = {} + if self.worsttimes.get(currentChannel): + self.worsttimes[currentChannel] = {} + + # No duck lauched + self.duck[currentChannel] = False + + # Reinit number of shoots + self.shoots[currentChannel] = 0 + + + + def _launch(self, irc, msg, args): + """ + Launch a duck + """ + currentChannel = msg.args[0] + if irc.isChannel(currentChannel): + if(self.started[currentChannel] == True): + if (self.duck[currentChannel] == False): + + # Store the time when the duck has been launched + self.times[currentChannel] = time.time() + + # Store the fact that there's a duck now + self.duck[currentChannel] = True + + # Send message directly (instead of queuing it with irc.reply) + irc.sendMsg(ircmsgs.privmsg(currentChannel, "\_o< quack!")) + + # Define a new throttle[currentChannel] for the next launch + self.throttle[currentChannel] = random.randint(self.minthrottle[currentChannel], self.maxthrottle[currentChannel]) + + try: + self.shoots[currentChannel] += 1 + except: + self.shoots[currentChannel] = 1 + else: + + irc.reply("Already a duck") + else: + irc.reply("The hunt has not started yet!") + else: + irc.error('You have to be on a channel') + + +Class = DuckHunt + +# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: diff --git a/DuckHunt/test.py b/DuckHunt/test.py new file mode 100644 index 0000000..20ebafe --- /dev/null +++ b/DuckHunt/test.py @@ -0,0 +1,47 @@ +### +# Copyright (c) 2012, Matthias Meusburger +# 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. +### + +from supybot.test import * + +class DuckHuntTestCase(ChannelPluginTestCase): + plugins = ('DuckHunt',) + + + def tests(self): + self.assertResponse("bang", "There is no hunt right now! You can start a hunt with the 'start' command") + self.assertResponse("stop", "Nothing to stop: there's no hunt right now.") + self.assertResponse("start", "The hunt starts now!") + self.assertResponse("start", "There is already a hunt right now!") + self.assertRegexp("bang", "^There was no duck!") + self.assertResponse("stop", "The hunt stops now!") + self.assertNotError("listscores") + self.assertNotError("weekscores") + + +# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: