mirror of
https://github.com/oddluck/limnoria-plugins.git
synced 2025-04-26 04:51:09 -05:00
Add 'WordGames/' from commit '854e77a6d4d576aad9da8aa9852a1939e205fc4e'
git-subtree-dir: WordGames git-subtree-mainline: a8c0694be8909bdc34ab3f23579b911545b94df6 git-subtree-split: 854e77a6d4d576aad9da8aa9852a1939e205fc4e
This commit is contained in:
commit
5e74a4a10e
188
WordGames/README.md
Normal file
188
WordGames/README.md
Normal file
@ -0,0 +1,188 @@
|
||||
Supybot Word Games Plugin
|
||||
=========================
|
||||
|
||||
This is a plugin for the popular IRC bot Supybot that implements a few
|
||||
word games to play in your IRC channel.
|
||||
|
||||
Typical installation process:
|
||||
|
||||
1. Clone this git repository in your downloads directory.
|
||||
2. Create a symlink called `Wordgames` in supybot/plugins pointing to it.
|
||||
3. In IRC, load Wordgames.
|
||||
|
||||
**Note:** These games rely on a dictionary file (not included). On
|
||||
Ubuntu, you can normally just install the `wamerican` package. See
|
||||
the Configurable Variables section to customize.
|
||||
|
||||
Commands
|
||||
--------
|
||||
|
||||
The following commands are exposed by this plugin:
|
||||
|
||||
`worddle [easy|medium|hard|evil | stop|stats]`
|
||||
|
||||
> Play a Worddle game. With no argument, a new game will start with the
|
||||
> default difficulty (see worddleDifficulty in the Configuration section).
|
||||
> If a game is already started, you will join the game in progress.
|
||||
>
|
||||
> In addition, you may use the following subcommands:
|
||||
>
|
||||
> easy minimum acceptable word length: 3 (overrides config)
|
||||
> medium minimum acceptable word length: 4 " "
|
||||
> hard minimum acceptable word length: 5 " "
|
||||
> evil minimum acceptable word length: 6 " "
|
||||
> stop Stop a currently running game (alias for @wordquit)
|
||||
> stats Display some post-game statistics about the board
|
||||
|
||||
`wordshrink [difficulty]`
|
||||
|
||||
> Start a new WordShrink game. Difficulty values: easy [medium] hard evil
|
||||
|
||||
`wordtwist [difficulty]`
|
||||
|
||||
> Start a new WordTwist game. Difficulty values: easy [medium] hard evil
|
||||
|
||||
`wordquit`
|
||||
|
||||
> Shut down any currently running game. One solution will be displayed for
|
||||
> the word chain games, to satisfy your curiosity.
|
||||
|
||||
Game Rules
|
||||
----------
|
||||
|
||||
### Worddle
|
||||
|
||||
Worddle is a clone of a well-known puzzle game involving a 4x4 grid of
|
||||
randomly-placed letters. It's a timed, multiplayer game where you compete
|
||||
to find words that your opponents didn't find.
|
||||
|
||||
When you start a new game, or join an existing game, the bot will send you a
|
||||
private message indicating that you are playing. All your guesses must be
|
||||
sent to the bot in this private conversation, so your opponents can't see your
|
||||
guesses. At the end of the game, the results will be presented in the channel
|
||||
where the game was started.
|
||||
|
||||
To be a valid guess, words must:
|
||||
|
||||
* be made of adjacent letters on the board (in all 8 directions, diagonals ok)
|
||||
* be at least 3 letters in length (or 4, or 5, etc. depending on the level)
|
||||
* appear in the dictionary file.
|
||||
|
||||
At the end of the game, if a word was found by multiple players, it is not
|
||||
counted. The remaining words contribute to your score using these values:
|
||||
|
||||
Length | Value
|
||||
--------+-------
|
||||
3, 4 | 1 point
|
||||
5 | 2 points
|
||||
6 | 3 points
|
||||
7 | 5 points
|
||||
8+ | 11 points
|
||||
|
||||
### WordShrink
|
||||
|
||||
WordShrink and WordTwist are word chain (or word ladder) style games.
|
||||
A puzzle will be presented in the form:
|
||||
|
||||
word1 > --- > --- > word4
|
||||
|
||||
... and your job is to come up with a response of the form `word2 > word3`
|
||||
that completes the puzzle. (You can optionally include the start and end
|
||||
words in your response, as long as each word is separated by a greater-than
|
||||
sign.)
|
||||
|
||||
In WordShrink, each word must be made by removing one letter from the
|
||||
preceding word and rearranging the letters to form a new word. Example
|
||||
session:
|
||||
|
||||
<mike> @wordshrink
|
||||
<supybot> WordShrink: lights > ----- > ---- > sit
|
||||
<supybot> (12 possible solutions)
|
||||
<mike> sight > this
|
||||
<supybot> WordShrink: mike got it!
|
||||
<supybot> WordShrink: lights > sight > this > sit
|
||||
<ben> lights > hilts > hits > sit
|
||||
<supybot> ben: Your solution is also valid.
|
||||
|
||||
### WordTwist
|
||||
|
||||
WordTwist is very similar to WordShrink, except that the way you manipulate
|
||||
the words to solve the puzzle is changed. In WordTwist, you change exactly
|
||||
one letter in each successive word to form a new word (no rearranging is
|
||||
allowed). Example session:
|
||||
|
||||
<mike> @wordtwist medium
|
||||
<supybot> WordTwist: mass > ---- > ---- > ---- > jade
|
||||
<supybot> (5 possible solutions)
|
||||
<mike> mars > mare > made
|
||||
<supybot> WordTwist: mike got it!
|
||||
<supybot> WordTwist: mass > mars > mare > made > jade
|
||||
|
||||
Configuration Variables
|
||||
-----------------------
|
||||
|
||||
`plugins.Wordgames.wordFile`
|
||||
|
||||
> Path to the dictionary file.
|
||||
>
|
||||
> Default: `/usr/share/dict/american-english`
|
||||
|
||||
`plugins.Wordgames.wordRegexp`
|
||||
|
||||
> A regular expression defining what a valid word looks like. This will
|
||||
> be used to filter words from the dictionary file that contain undesirable
|
||||
> characters (proper names, hyphens, accents, etc.). You will probably have
|
||||
> to quote the string when setting, e.g.:
|
||||
>
|
||||
> @config plugins.Wordgames.wordRegexp "^[a-x]+$"
|
||||
>
|
||||
> (No words containing 'y' or 'z' would be allowed by this.)
|
||||
>
|
||||
> Default: `^[a-z]+$`
|
||||
|
||||
`plugins.Wordgames.worddleDelay`
|
||||
|
||||
> The length (in seconds) of the pre-game period where players can join a
|
||||
> new Worddle game.
|
||||
>
|
||||
> Default: `15`
|
||||
|
||||
`plugins.Wordgames.worddleDuration`
|
||||
|
||||
> The length (in seconds) of the active period of a Worddle game, when
|
||||
> players can submit guesses.
|
||||
>
|
||||
> Default: `90`
|
||||
|
||||
`plugins.Wordgames.worddleDifficulty`
|
||||
|
||||
> The default difficulty for a Worddle game.
|
||||
>
|
||||
> Default: `easy` (words must be 3 letters or longer)
|
||||
|
||||
A Technical Note About Worddle
|
||||
------------------------------
|
||||
|
||||
This game sends a lot of PRIVMSGs (between the channel and all the players,
|
||||
the messages add up). It attempts to send them in a single PRIVMSG if
|
||||
possible to combine targets.
|
||||
|
||||
Supybot: I run Supybot with the latest git (there are show-stopper bugs in
|
||||
0.83.4.1) and the Twisted driver (but Socket should work as well).
|
||||
|
||||
IRC Server: I had to tune my IRC server to handle this game, due to the large
|
||||
amount of messages it sends. You may find that it has problems on your server
|
||||
due to flood controls (bot may be either fake lagged or kicked from the
|
||||
server). If the game seems extremely slow, it is either an old Supybot or the
|
||||
server is throttling you.
|
||||
|
||||
I would like to add an option to tune the verbosity of the game to mitigate
|
||||
this for restrictive servers.
|
||||
|
||||
Credit
|
||||
------
|
||||
|
||||
Copyright 2012 Mike Mueller <mike@subfocal.net>
|
||||
Released under the WTF public license: http://sam.zoy.org/wtfpl/
|
||||
|
||||
Thanks to Ben Schomp <ben@benschomp.com> for the inspiration and QA testing.
|
59
WordGames/__init__.py
Normal file
59
WordGames/__init__.py
Normal file
@ -0,0 +1,59 @@
|
||||
###
|
||||
# Copyright (c) 2012, Mike Mueller
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are met:
|
||||
#
|
||||
# * Do whatever you want.
|
||||
#
|
||||
# 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.
|
||||
|
||||
###
|
||||
|
||||
"""
|
||||
Implements some word games.
|
||||
"""
|
||||
|
||||
import supybot
|
||||
import supybot.world as world
|
||||
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__ = ""
|
||||
|
||||
# Replace this with an appropriate author or supybot.Author instance.
|
||||
__author__ = supybot.Author('Mike Mueller', 'mmueller', 'mike@subfocal.net')
|
||||
|
||||
# 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__ = 'http://github.com/mmueller/supybot-wordgames'
|
||||
|
||||
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:
|
61
WordGames/config.py
Normal file
61
WordGames/config.py
Normal file
@ -0,0 +1,61 @@
|
||||
###
|
||||
# Copyright (c) 2012, Mike Mueller
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are met:
|
||||
#
|
||||
# * Do whatever you want
|
||||
#
|
||||
# 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
|
||||
|
||||
import re
|
||||
|
||||
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('Wordgames', True)
|
||||
|
||||
Wordgames = conf.registerPlugin('Wordgames')
|
||||
|
||||
conf.registerGlobalValue(Wordgames, 'wordFile',
|
||||
registry.String('/usr/share/dict/american-english',
|
||||
'Path to the dictionary file.'))
|
||||
|
||||
conf.registerGlobalValue(Wordgames, 'wordRegexp',
|
||||
registry.String('^[a-z]+$',
|
||||
'Regular expression defining what a valid word looks ' +
|
||||
'like (i.e. ignore proper names, contractions, etc. ' +
|
||||
'Modify this if you need to allow non-English chars.'))
|
||||
|
||||
conf.registerGlobalValue(Wordgames, 'worddleDelay',
|
||||
registry.NonNegativeInteger(15,
|
||||
'Delay (in seconds) before a Worddle game ' +
|
||||
'begins.'))
|
||||
|
||||
conf.registerGlobalValue(Wordgames, 'worddleDuration',
|
||||
registry.NonNegativeInteger(90,
|
||||
'Duration (in seconds) of a Worddle game ' +
|
||||
'(not including the initial delay).'))
|
||||
|
||||
conf.registerGlobalValue(Wordgames, 'worddleDifficulty',
|
||||
registry.String('easy', 'Default difficulty for Worddle games.'))
|
||||
|
||||
# vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79:
|
1040
WordGames/plugin.py
Normal file
1040
WordGames/plugin.py
Normal file
File diff suppressed because it is too large
Load Diff
27
WordGames/test.py
Normal file
27
WordGames/test.py
Normal file
@ -0,0 +1,27 @@
|
||||
# Copyright (c) 2012, Mike Mueller
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are met:
|
||||
#
|
||||
# * Do whatever you want.
|
||||
#
|
||||
# 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 WordgamesTestCase(PluginTestCase):
|
||||
plugins = ('Wordgames',)
|
||||
|
||||
|
||||
# vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79:
|
108
WordGames/trie.py
Normal file
108
WordGames/trie.py
Normal file
@ -0,0 +1,108 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
Quick & dirty prefix tree (aka trie).
|
||||
"""
|
||||
|
||||
# This got a little uglier because using a nice object-oriented approach took
|
||||
# too much time and memory on big trees. Now every node is simply a dict,
|
||||
# with the special '*' field meaning that the word is complete at that node.
|
||||
|
||||
class Trie(object):
|
||||
def __init__(self):
|
||||
self.contents = {'*': False}
|
||||
|
||||
def add(self, value, contents=None):
|
||||
if contents is None:
|
||||
contents = self.contents
|
||||
if not value:
|
||||
contents['*'] = True
|
||||
return
|
||||
prefix = value[0]
|
||||
remainder = value[1:]
|
||||
child_contents = contents.get(prefix, None)
|
||||
if not child_contents:
|
||||
child_contents = {'*': False}
|
||||
contents[prefix] = child_contents
|
||||
self.add(remainder, child_contents)
|
||||
|
||||
def find(self, value):
|
||||
"Return true if the value appears, false otherwise."
|
||||
x = self.find_prefix(value)
|
||||
return x and x['*'] == True
|
||||
|
||||
def find_prefix(self, value, contents=None):
|
||||
"Return true if the given prefix appears in the tree."
|
||||
if contents is None:
|
||||
contents = self.contents
|
||||
if not value:
|
||||
return contents
|
||||
child_contents = contents.get(value[0], None)
|
||||
if not child_contents:
|
||||
return None
|
||||
return self.find_prefix(value[1:], child_contents)
|
||||
|
||||
def dump(self, indent=0, contents=None):
|
||||
"Dump the trie to stdout."
|
||||
if contents is None:
|
||||
contents = self.contents
|
||||
for key in sorted(contents.keys()):
|
||||
if key == '*':
|
||||
continue
|
||||
text = indent * ' '
|
||||
text += key
|
||||
child_contents = contents[key]
|
||||
if child_contents['*']:
|
||||
text += '*'
|
||||
print(text)
|
||||
self.dump(indent+2, child_contents)
|
||||
|
||||
if __name__ == '__main__':
|
||||
import resource
|
||||
import sys
|
||||
import time
|
||||
|
||||
if '--perf' in sys.argv:
|
||||
# Performance test, last arg should be input file
|
||||
start = time.time()
|
||||
t = Trie()
|
||||
f = open(sys.argv[-1], 'r')
|
||||
for line in f:
|
||||
t.add(line.strip())
|
||||
mem = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss
|
||||
elapsed = time.time() - start
|
||||
print(('Trie created in %g seconds.' % elapsed))
|
||||
print(('Used %dMB RAM.' % (mem/1024)))
|
||||
else:
|
||||
# Regular sanity test
|
||||
t = Trie()
|
||||
t.add('hell')
|
||||
t.add('hello')
|
||||
t.add('he')
|
||||
t.add('world')
|
||||
t.add('alphabet')
|
||||
t.add('foo')
|
||||
t.add('food')
|
||||
t.add('foodie')
|
||||
t.add('bar')
|
||||
t.add('alphanumeric')
|
||||
t.dump()
|
||||
|
||||
assert not t.find('h')
|
||||
assert t.find('he')
|
||||
assert not t.find('hel')
|
||||
assert t.find('hell')
|
||||
assert t.find('hello')
|
||||
assert not t.find('r')
|
||||
assert t.find('world')
|
||||
assert not t.find('ba')
|
||||
assert t.find('bar')
|
||||
assert t.find('alphabet')
|
||||
assert t.find('alphanumeric')
|
||||
assert not t.find('alpha')
|
||||
assert not t.find('f')
|
||||
assert not t.find('fo')
|
||||
assert t.find('foo')
|
||||
assert t.find('food')
|
||||
assert not t.find('foodi')
|
||||
assert t.find('foodie')
|
Loading…
x
Reference in New Issue
Block a user