Wordgames -> WordGames

This commit is contained in:
oddluck 2020-03-01 08:18:43 +00:00
parent 366da60c58
commit fe6541cac0
2 changed files with 75 additions and 77 deletions

View File

@ -39,31 +39,31 @@ def configure(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)
conf.registerPlugin('WordGames', True)
Wordgames = conf.registerPlugin('Wordgames')
WordGames = conf.registerPlugin('WordGames')
conf.registerGlobalValue(Wordgames, 'wordFile',
conf.registerGlobalValue(WordGames, 'wordFile',
registry.String('/usr/share/dict/american-english',
'Path to the dictionary file.'))
conf.registerGlobalValue(Wordgames, 'wordRegexp',
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',
conf.registerGlobalValue(WordGames, 'boggleDelay',
registry.NonNegativeInteger(15,
'Delay (in seconds) before a Worddle game ' +
'Delay (in seconds) before a Boggle game ' +
'begins.'))
conf.registerGlobalValue(Wordgames, 'worddleDuration',
registry.NonNegativeInteger(90,
'Duration (in seconds) of a Worddle game ' +
conf.registerGlobalValue(WordGames, 'boggleDuration',
registry.NonNegativeInteger(120,
'Duration (in seconds) of a Boggle game ' +
'(not including the initial delay).'))
conf.registerGlobalValue(Wordgames, 'worddleDifficulty',
registry.String('easy', 'Default difficulty for Worddle games.'))
conf.registerGlobalValue(WordGames, 'boggleDifficulty',
registry.String('easy', 'Default difficulty for Boggle games.'))
# vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79:

View File

@ -62,13 +62,13 @@ GRAY = '\x0314'
LGRAY = '\x0315'
def debug(message):
log.debug('Wordgames: ' + message)
log.debug('WordGames: ' + message)
def info(message):
log.info('Wordgames: ' + message)
log.info('WordGames: ' + message)
def error(message):
log.error('Wordgames: ' + message)
log.error('WordGames: ' + message)
def point_str(value):
"Return 'point' or 'points' depending on value."
@ -97,7 +97,7 @@ def get_max_targets(irc):
error('Detecting max targets: %s. Using default (1).' % str(e))
return result
class WordgamesError(Exception): pass
class WordGamesError(Exception): pass
class Difficulty:
EASY = 0
@ -117,9 +117,9 @@ class Difficulty:
try:
return Difficulty.VALUES[Difficulty.NAMES.index(name)]
except ValueError:
raise WordgamesError('Unrecognized difficulty value: %s' % name)
raise WordGamesError('Unrecognized difficulty value: %s' % name)
class Wordgames(callbacks.Plugin):
class WordGames(callbacks.Plugin):
"Please see the README file to configure and use this plugin."
def inFilter(self, irc, msg):
@ -135,16 +135,16 @@ class Wordgames(callbacks.Plugin):
game.guess(msg.nick, msg.args[1])
return None
except:
return
pass
# In all other cases, default to normal message handling
return self.parent.inFilter(irc, msg)
return msg
def __init__(self, irc):
# Tech note: Save a reference to my parent class because Supybot's
# Owner plugin will reload this module BEFORE calling die(), which
# means super() calls will fail with a TypeError. I consider this a
# bug in Supybot.
self.parent = super(Wordgames, self)
self.parent = super(WordGames, self)
self.parent.__init__(irc)
self.games = {}
@ -170,55 +170,53 @@ class Wordgames(callbacks.Plugin):
irc.reply('No game is currently running.')
wordsolve = wrap(wordsolve, ['channel'])
def worddle(self, irc, msgs, args, channel, command):
def boggle(self, irc, msgs, args, channel, command):
"""[command]
Play a Worddle game. Commands: [easy|medium|hard|evil | stop|stats]
Play a game of Boggle. Commands: [easy|medium|hard|evil] [stop|stats]
(default: start with configured difficulty).
"""
try:
# Allow deprecated 'join' command:
if not command or command == 'join' or command in Difficulty.NAMES:
difficulty = Difficulty.value(
self.registryValue('worddleDifficulty'))
self.registryValue('boggleDifficulty'))
if command in Difficulty.NAMES:
difficulty = Difficulty.value(command)
game = self.games.get(channel)
if game and game.is_running():
if game.__class__ == Worddle:
if game.__class__ == Boggle:
if command:
irc.reply('Joining the game. (Ignored "%s".)' %
command)
game.join(msgs.nick)
else:
irc.reply('Current word game is not Worddle!')
irc.reply('Current word game is not Boggle!')
else:
delay = self.registryValue('worddleDelay')
duration = self.registryValue('worddleDuration')
self._start_game(Worddle, irc, channel, msgs.nick,
delay = self.registryValue('boggleDelay')
duration = self.registryValue('boggleDuration')
self._start_game(Boggle, irc, channel, msgs.nick,
delay, duration, difficulty)
elif command == 'stop':
# Alias for @wordquit
self._stop_game(irc, channel)
elif command == 'stats':
game = self.games.get(channel)
if not game or game.__class__ != Worddle:
irc.reply('No Worddle game available for stats.')
if not game or game.__class__ != Boggle:
irc.reply('No Boggle game available for stats.')
elif game.is_running():
irc.reply('Please wait until the game finishes.')
else:
game.stats()
else:
irc.reply('Unrecognized command to worddle.')
except WordgamesError as e:
irc.reply('Wordgames error: %s' % str(e))
irc.reply('Unrecognized command to Boggle.')
except WordGamesError as e:
irc.reply('WordGames error: %s' % str(e))
irc.reply('Please check the configuration and try again. ' +
'See README for help.')
worddle = wrap(worddle,
boggle = wrap(boggle,
['channel', optional(('literal',
Difficulty.NAMES + ['join', 'stop', 'stats']))])
# Alias for misspelling of the game name
wordle = worddle
def wordshrink(self, irc, msgs, args, channel, difficulty):
"""[easy|medium|hard|evil] (default: medium)
@ -268,12 +266,12 @@ class Wordgames(callbacks.Plugin):
try:
regexp = re.compile(self.registryValue('wordRegexp'))
except Exception as e:
raise WordgamesError("Bad value for wordRegexp: %s" % str(e))
raise WordGamesError("Bad value for wordRegexp: %s" % str(e))
path = self.registryValue('wordFile')
try:
wordFile = open(path)
except Exception as e:
raise WordgamesError("Unable to open word file: %s" % path)
raise WordGamesError("Unable to open word file: %s" % path)
return list(filter(regexp.match, list(map(str.strip, wordFile.readlines()))))
def _start_game(self, Game, irc, channel, *args, **kwargs):
@ -286,10 +284,10 @@ class Wordgames(callbacks.Plugin):
words = self._get_words()
self.games[channel] = Game(words, irc, channel, *args, **kwargs)
self.games[channel].start()
except WordgamesError as e:
except WordGamesError as e:
# Get rid of the game in case it's in an indeterminate state
if channel in self.games: del self.games[channel]
irc.reply('Wordgames error: %s' % str(e))
irc.reply('WordGames error: %s' % str(e))
irc.reply('Please check the configuration and try again. ' +
'See README for help.')
@ -364,8 +362,8 @@ class BaseGame(object):
"Handle incoming messages on the channel."
pass
class Worddle(BaseGame):
"The Worddle game implementation."
class Boggle(BaseGame):
"The Boggle game implementation."
BOARD_SIZE = 4
FREQUENCY_TABLE = {
@ -401,7 +399,7 @@ class Worddle(BaseGame):
'point%%(plural)s (%%(words)s)') %
(WHITE, LGRAY, LGREEN, LGRAY),
'startup': ('Starting in %%(seconds)d seconds, ' +
'use "%s%%(commandChar)sworddle%s" to play!') %
'use "%s%%(commandChar)sboggle%s" to play!') %
(WHITE, LGRAY),
'stopped': 'Game stopped.',
'stopped2': ('%s::: Game Stopped :::%s') % (LRED, LGRAY),
@ -451,7 +449,7 @@ class Worddle(BaseGame):
def get_score(self):
score = 0
for word in self.unique:
score += Worddle.POINT_VALUES.get(len(word), Worddle.MAX_POINTS)
score += Boggle.POINT_VALUES.get(len(word), Boggle.MAX_POINTS)
return score
def render_words(self, longest_len=0):
@ -496,14 +494,14 @@ class Worddle(BaseGame):
else:
unique.add(word)
self.player_results[player] = \
Worddle.PlayerResult(player, unique, dup)
Boggle.PlayerResult(player, unique, dup)
def sorted_results(self):
return sorted(list(self.player_results.values()), reverse=True)
def __init__(self, words, irc, channel, nick, delay, duration, difficulty):
# See tech note in the Wordgames class.
self.parent = super(Worddle, self)
# See tech note in the WordGames class.
self.parent = super(Boggle, self)
self.parent.__init__(words, irc, channel)
self.delay = delay
self.duration = duration
@ -511,11 +509,11 @@ class Worddle(BaseGame):
self.max_targets = get_max_targets(irc)
self._handle_difficulty()
self.board = self._generate_board()
self.event_name = 'Worddle.%d' % id(self)
self.event_name = 'Boggle.%d' % id(self)
self.init_time = time.time()
self.longest_len = len(max(self.board.solutions, key=len))
self.starter = nick
self.state = Worddle.State.PREGAME
self.state = Boggle.State.PREGAME
self.players = []
self.player_answers = {}
self.warnings = [30, 10, 5]
@ -527,7 +525,7 @@ class Worddle(BaseGame):
if nick not in self.players:
self.join(nick)
# Pre-game messages are relayed as chatter (not treated as guesses)
if self.state < Worddle.State.ACTIVE:
if self.state < Boggle.State.ACTIVE:
self._broadcast('chat', self.players, nick=nick, text=text)
return
guesses = set(map(str.lower, text.split()))
@ -548,7 +546,7 @@ class Worddle(BaseGame):
def join(self, nick):
assert self.is_running()
assert self.state != Worddle.State.DONE
assert self.state != Boggle.State.DONE
if nick not in self.players:
self._broadcast('welcome1', [nick], now=True,
difficulty=Difficulty.name(self.difficulty),
@ -557,12 +555,12 @@ class Worddle(BaseGame):
self._broadcast('joined', self.players, nick=nick)
self.players.append(nick)
self.player_answers[nick] = set()
if self.state == Worddle.State.ACTIVE:
if self.state == Boggle.State.ACTIVE:
self._display_board(nick)
else:
self._broadcast('players', [nick])
# Keep at least 5 seconds on the pre-game clock if someone joins
if self.state == Worddle.State.PREGAME:
if self.state == Boggle.State.PREGAME:
time_left = self.init_time + self.delay - time.time()
if time_left < 5:
self.delay += (5 - time_left)
@ -572,7 +570,7 @@ class Worddle(BaseGame):
def show(self):
# Not sure if this is really useful.
#if self.state == Worddle.State.ACTIVE:
#if self.state == Boggle.State.ACTIVE:
# self._display_board(self.channel)
pass
@ -587,7 +585,7 @@ class Worddle(BaseGame):
def stop(self, now=False):
self.parent.stop()
self.state = Worddle.State.DONE
self.state = Boggle.State.DONE
try:
schedule.removeEvent(self.event_name)
except KeyError:
@ -597,10 +595,10 @@ class Worddle(BaseGame):
self._broadcast('stopped2', self.players)
def stats(self):
assert self.state == Worddle.State.DONE
assert self.state == Boggle.State.DONE
points = 0
for word in self.board.solutions:
points += Worddle.POINT_VALUES.get(len(word), Worddle.MAX_POINTS)
points += Boggle.POINT_VALUES.get(len(word), Boggle.MAX_POINTS)
longest_words = [w for w in self.board.solutions if len(w) == self.longest_len]
self.announce(('There were %s%d%s possible words, with total point'
' value %s%d%s. The longest word%s: %s%s%s.') %
@ -635,7 +633,7 @@ class Worddle(BaseGame):
(WHITE, (LGRAY + ', ' + WHITE).join(self.players), LGRAY)
if 'points' in kwargs:
kwargs['plural'] = '' if kwargs['points'] == 1 else 's'
formatted = Worddle.MESSAGES[name] % kwargs
formatted = Boggle.MESSAGES[name] % kwargs
self._broadcast_text(formatted, recipients, now)
def _handle_difficulty(self):
@ -647,12 +645,12 @@ class Worddle(BaseGame):
}[self.difficulty]
def _get_ready(self):
self.state = Worddle.State.READY
self.state = Boggle.State.READY
self._broadcast('ready', now=True)
self._schedule_next_event()
def _begin_game(self):
self.state = Worddle.State.ACTIVE
self.state = Boggle.State.ACTIVE
self.start_time = time.time()
self.end_time = self.start_time + self.duration
self._display_board()
@ -668,15 +666,15 @@ class Worddle(BaseGame):
schedule.removeEvent(self.event_name)
except KeyError:
pass
if self.state == Worddle.State.PREGAME:
if self.state == Boggle.State.PREGAME:
# Schedule "get ready" message
schedule.addEvent(self._get_ready,
self.init_time + self.delay, self.event_name)
elif self.state == Worddle.State.READY:
elif self.state == Boggle.State.READY:
# Schedule game start
schedule.addEvent(self._begin_game,
self.init_time + self.delay + 3, self.event_name)
elif self.state == Worddle.State.ACTIVE:
elif self.state == Boggle.State.ACTIVE:
if self.warnings:
# Warn almost half a second early, in case there is a little
# latency before the event is triggered. (Otherwise a 30 second
@ -697,10 +695,10 @@ class Worddle(BaseGame):
def _end_game(self):
self.gameover()
self.state = Worddle.State.DONE
self.state = Boggle.State.DONE
# Compute results
results = Worddle.Results()
results = Boggle.Results()
for player, answers in self.player_answers.items():
results.add_player_words(player, answers)
@ -728,7 +726,7 @@ class Worddle(BaseGame):
def _display_board(self, nick=None):
"Display the board to everyone or just one nick if specified."
commandChar = str(conf.supybot.reply.whenAddressedBy.chars)[0]
help_msgs = [''] * Worddle.BOARD_SIZE
help_msgs = [''] * Boggle.BOARD_SIZE
help_msgs[1] = '%sLet\'s GO!' % (WHITE)
help_msgs[2] = '%s%s%s seconds left!' % \
(LYELLOW, int(round(self.end_time - time.time())), LGRAY)
@ -744,16 +742,16 @@ class Worddle(BaseGame):
attempts = 5
wordtrie = Trie()
list(map(wordtrie.add, self.words))
boards = [WorddleBoard(wordtrie, Worddle.BOARD_SIZE, self.min_length)
boards = [BoggleBoard(wordtrie, Boggle.BOARD_SIZE, self.min_length)
for i in range(0, attempts)]
board_quality = lambda b: len(b.solutions)
return max(boards, key=board_quality)
class WorddleBoard(object):
"Represents the board in a Worddle game."
class BoggleBoard(object):
"Represents the board in a Boggle game."
def __init__(self, wordtrie, n, min_length):
"Generate a new n x n Worddle board."
"Generate a new n x n Boggle board."
self.size = n
self.min_length = min_length
self.rows = self._generate_rows()
@ -798,10 +796,10 @@ class WorddleBoard(object):
return result
def _generate_rows(self):
"Randomly generate a Worddle board (a list of lists)."
"Randomly generate a Boggle board (a list of lists)."
letters = reduce(add, (list(map(mul,
list(Worddle.FREQUENCY_TABLE.keys()),
list(Worddle.FREQUENCY_TABLE.values())))))
list(Boggle.FREQUENCY_TABLE.keys()),
list(Boggle.FREQUENCY_TABLE.values())))))
rows = []
values = random.sample(letters, self.size**2)
for i in range(0, self.size):
@ -831,7 +829,7 @@ class WordChain(BaseGame):
self.num_solutions = num_solutions
def __init__(self, words, irc, channel, settings):
# See tech note in the Wordgames class.
# See tech note in the WordGames class.
self.parent = super(WordChain, self)
self.parent.__init__(words, irc, channel)
self.settings = settings
@ -853,7 +851,7 @@ class WordChain(BaseGame):
while len(self.solution) < self.solution_length:
attempts -= 1
if attempts == 0:
raise WordgamesError(('Unable to generate %s puzzle. This' +
raise WordGamesError(('Unable to generate %s puzzle. This' +
' is either a bug, or the word file is too small.') %
self.__class__.__name__)
self.solution = [random.choice(self.words)]
@ -877,7 +875,7 @@ class WordChain(BaseGame):
if happy:
break
if not happy:
raise WordgamesError(('Unable to generate %s puzzle meeting the ' +
raise WordGamesError(('Unable to generate %s puzzle meeting the ' +
'game parameters. This is probably a bug.') %
self.__class__.__name__)
@ -1064,6 +1062,6 @@ class WordTwist(WordChain):
"If it's possible to get there in fewer hops, this is trivial."
return len(solution) < self.solution_length
Class = Wordgames
Class = WordGames
# vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: