the bot will generate acronyms of 3-8 characters in length. it's your job to find the "best" phrase fitting the acronym. work in progress, but this is still playable
368 lines
12 KiB
Python
368 lines
12 KiB
Python
"""
|
|
Acro - An acromania style game for IRC
|
|
Copyright (C) 2012 Brian S. Stephan
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
"""
|
|
|
|
import random
|
|
import re
|
|
import sqlite3
|
|
import thread
|
|
import time
|
|
|
|
from extlib import irclib
|
|
|
|
from Module import Module
|
|
|
|
class Acro(Module):
|
|
|
|
class AcroGame(object):
|
|
|
|
"""Track game details."""
|
|
|
|
def __init__(self):
|
|
"""Initialize basic stuff."""
|
|
|
|
# running state
|
|
self.state = 0
|
|
self.quit = False
|
|
self.scores = dict()
|
|
self.rounds = []
|
|
self.connection = None
|
|
self.channel = ''
|
|
|
|
class AcroRound(object):
|
|
|
|
"""Track a particular round of a game."""
|
|
|
|
def __init__(self):
|
|
"""Initialize basic stuff."""
|
|
|
|
self.acro = ""
|
|
self.submissions = dict()
|
|
self.sub_shuffle = []
|
|
self.votes = dict()
|
|
|
|
# default options
|
|
self.seconds_to_submit = 45
|
|
self.seconds_to_vote = 30
|
|
self.seconds_to_pause = 5
|
|
|
|
"""
|
|
Play a game where users come up with silly definitions for randomly-generated
|
|
acronyms.
|
|
"""
|
|
|
|
def __init__(self, irc, config, server):
|
|
"""Set up the game tracking and such."""
|
|
|
|
# build regexes
|
|
acroprefix = r"!acro"
|
|
statuscmd = r"status"
|
|
startcmd = r"start"
|
|
submitcmd = r"submit"
|
|
votecmd = r"vote"
|
|
quitcmd = r"quit"
|
|
|
|
self.statusre = re.compile(r"^{0:s}\s+{1:s}$".format(acroprefix, statuscmd))
|
|
self.startre = re.compile(r"^{0:s}\s+{1:s}$".format(acroprefix, startcmd))
|
|
self.submitre = re.compile(r"^{0:s}\s+{1:s}\s+(.*)$".format(acroprefix, submitcmd))
|
|
self.votere = re.compile(r"^{0:s}\s+{1:s}\s+(\d+)$".format(acroprefix, votecmd))
|
|
self.quitre = re.compile(r"^{0:s}\s+{1:s}$".format(acroprefix, quitcmd))
|
|
|
|
# game state
|
|
self.game = self.AcroGame()
|
|
|
|
Module.__init__(self, irc, config, server)
|
|
|
|
def do(self, connection, event, nick, userhost, what, admin_unlocked):
|
|
"""Handle commands and inputs."""
|
|
|
|
target = event.target()
|
|
|
|
if self.statusre.search(what):
|
|
return self.reply(connection, event, self.do_status(what))
|
|
if self.startre.search(what):
|
|
return self.reply(connection, event, self.do_start(connection, target, what))
|
|
if self.submitre.search(what):
|
|
match = self.submitre.search(what)
|
|
return self.reply(connection, event, self.do_submit(nick, match.group(1)))
|
|
if self.votere.search(what):
|
|
match = self.votere.search(what)
|
|
return self.reply(connection, event, self.do_vote(nick, match.group(1)))
|
|
if self.quitre.search(what):
|
|
return self.reply(connection, event, self.do_quit(what))
|
|
|
|
def do_status(self, what):
|
|
"""Return the status of the currently-running game, if there is one."""
|
|
|
|
if self.game.state == 0:
|
|
return "there currently isn't a game running."
|
|
else:
|
|
return "the game is running."
|
|
|
|
def do_start(self, connection, target, what):
|
|
"""Start the game, notify the channel, and begin timers."""
|
|
|
|
if self.game.state != 0:
|
|
return "the game is alredy running."
|
|
else:
|
|
if irclib.is_channel(target):
|
|
self._start_new_game(connection, target)
|
|
else:
|
|
return "you must start the game from a channel."
|
|
|
|
def do_submit(self, nick, what):
|
|
"""Take a submission for an acro."""
|
|
|
|
return self._take_acro_submission(nick, what)
|
|
|
|
def do_vote(self, nick, what):
|
|
"""Take a vote for an acro."""
|
|
|
|
return self._take_acro_vote(nick, what)
|
|
|
|
def do_quit(self, what):
|
|
"""Quit the game after the current round ends."""
|
|
|
|
if self.game.state != 0:
|
|
self.game.quit = True
|
|
return "the game will end after the current round."
|
|
|
|
def thread_do_process_submissions(self, sleep_time):
|
|
"""Wait for players to provide acro submissions, and then kick off voting."""
|
|
|
|
time.sleep(sleep_time)
|
|
self._start_voting()
|
|
|
|
def thread_do_process_votes(self):
|
|
"""Wait for players to provide votes, and then continue or quit."""
|
|
|
|
time.sleep(self.game.rounds[-1].seconds_to_vote)
|
|
self._end_current_round()
|
|
|
|
def _start_new_game(self, connection, channel):
|
|
"""Begin a new game, which will have multiple rounds."""
|
|
|
|
self.game.state = 1
|
|
self.game.connection = connection
|
|
self.game.channel = channel
|
|
self.sendmsg(self.game.connection, self.game.channel,
|
|
"starting a new game of acro. it will run until you tell it to quit.")
|
|
|
|
self._start_new_round()
|
|
|
|
def _start_new_round(self):
|
|
"""Start a new round for play."""
|
|
|
|
self.game.state = 2
|
|
self.game.rounds.append(self.AcroRound())
|
|
acro = self._generate_acro()
|
|
self.game.rounds[-1].acro = acro
|
|
sleep_time = self.game.rounds[-1].seconds_to_submit + (5*(len(acro)-3))
|
|
|
|
self.sendmsg(self.game.connection, self.game.channel,
|
|
"the round has started! your acronym is '{0:s}'. ".format(acro) +
|
|
"submit within {0:d} seconds via !acro submit [meaning]".format(sleep_time))
|
|
|
|
thread.start_new_thread(self.thread_do_process_submissions, (sleep_time,))
|
|
|
|
def _generate_acro(self):
|
|
"""
|
|
Generate an acro to play with.
|
|
|
|
Letter frequencies pinched from
|
|
http://www.math.cornell.edu/~mec/2003-2004/cryptography/subs/frequencies.html
|
|
"""
|
|
|
|
acro = []
|
|
# generate acro 3-8 characters long
|
|
for i in range(1, random.randint(4, 9)):
|
|
letter = random.randint(1, 182303)
|
|
if letter <= 21912:
|
|
acro.append('E')
|
|
elif letter <= 38499:
|
|
acro.append('T')
|
|
elif letter <= 53309:
|
|
acro.append('A')
|
|
elif letter <= 67312:
|
|
acro.append('O')
|
|
elif letter <= 80630:
|
|
acro.append('I')
|
|
elif letter <= 93296:
|
|
acro.append('N')
|
|
elif letter <= 104746:
|
|
acro.append('S')
|
|
elif letter <= 115723:
|
|
acro.append('R')
|
|
elif letter <= 126518:
|
|
acro.append('H')
|
|
elif letter <= 134392:
|
|
acro.append('D')
|
|
elif letter <= 141645:
|
|
acro.append('L')
|
|
elif letter <= 146891:
|
|
acro.append('U')
|
|
elif letter <= 151834:
|
|
acro.append('C')
|
|
elif letter <= 156595:
|
|
acro.append('M')
|
|
elif letter <= 160795:
|
|
acro.append('F')
|
|
elif letter <= 164648:
|
|
acro.append('Y')
|
|
elif letter <= 168467:
|
|
acro.append('W')
|
|
elif letter <= 172160:
|
|
acro.append('G')
|
|
elif letter <= 175476:
|
|
acro.append('P')
|
|
elif letter <= 178191:
|
|
acro.append('B')
|
|
elif letter <= 180210:
|
|
acro.append('V')
|
|
elif letter <= 181467:
|
|
acro.append('K')
|
|
elif letter <= 181782:
|
|
acro.append('X')
|
|
elif letter <= 181987:
|
|
acro.append('Q')
|
|
elif letter <= 182175:
|
|
acro.append('J')
|
|
elif letter <= 182303:
|
|
acro.append('Z')
|
|
|
|
return "".join(acro)
|
|
|
|
def _take_acro_submission(self, nick, submission):
|
|
"""Take an acro submission and record it."""
|
|
|
|
if self.game.state == 2:
|
|
sub_acro = self._turn_text_into_acro(submission)
|
|
|
|
if sub_acro == self.game.rounds[-1].acro:
|
|
self.game.rounds[-1].submissions[nick] = submission
|
|
return "your submission has been recorded. it replaced any old submission for this round."
|
|
else:
|
|
return "the current acro is '{0:s}', not '{1:s}'".format(self.game.rounds[-1].acro, sub_acro)
|
|
|
|
def _turn_text_into_acro(self, text):
|
|
"""Turn text into an acronym."""
|
|
|
|
words = text.split()
|
|
acro = []
|
|
for w in words:
|
|
acro.append(w[0].upper())
|
|
return "".join(acro)
|
|
|
|
def _start_voting(self):
|
|
"""Begin the voting period."""
|
|
|
|
self.game.state = 3
|
|
self.game.rounds[-1].sub_shuffle = self.game.rounds[-1].submissions.keys()
|
|
random.shuffle(self.game.rounds[-1].sub_shuffle)
|
|
self.sendmsg(self.game.connection, self.game.channel,
|
|
"here are the results. vote with !acro vote [number]")
|
|
self._print_round_acros()
|
|
|
|
thread.start_new_thread(self.thread_do_process_votes, ())
|
|
|
|
def _print_round_acros(self):
|
|
"""Take the current round's acros and spit them to the channel."""
|
|
|
|
i = 0
|
|
for s in self.game.rounds[-1].sub_shuffle:
|
|
print(str(i) + " is " + s)
|
|
self.sendmsg(self.game.connection, self.game.channel,
|
|
" {0:d}: {1:s}".format(i+1, self.game.rounds[-1].submissions[s]))
|
|
i += 1
|
|
|
|
def _take_acro_vote(self, nick, vote):
|
|
"""Take an acro vote and record it."""
|
|
|
|
if self.game.state == 3:
|
|
acros = self.game.rounds[-1].submissions
|
|
if int(vote) > 0 and int(vote) <= len(acros):
|
|
print(vote + " is " + self.game.rounds[-1].sub_shuffle[int(vote)-1])
|
|
key = self.game.rounds[-1].sub_shuffle[int(vote)-1]
|
|
|
|
if key != nick:
|
|
self.game.rounds[-1].votes[nick] = key
|
|
return "your vote has been recorded. it replaced any old vote for this round."
|
|
else:
|
|
return "you can't vote for yourself!"
|
|
else:
|
|
return "you must vote for 1-{0:d}".format(len(acros))
|
|
|
|
def _end_current_round(self):
|
|
"""Clean up and output for ending the current round."""
|
|
|
|
self.game.state = 4
|
|
self.sendmsg(self.game.connection, self.game.channel,
|
|
"voting's over! here are the scores for the round:")
|
|
self._print_round_scores()
|
|
self._add_round_scores_to_game_scores()
|
|
|
|
# delay a bit
|
|
time.sleep(self.game.rounds[-1].seconds_to_pause)
|
|
self._continue_or_quit()
|
|
|
|
def _print_round_scores(self):
|
|
"""For the acros in the round, find the votes for them."""
|
|
|
|
i = 0
|
|
for s in self.game.rounds[-1].submissions.keys():
|
|
votes = filter(lambda x: x == s, self.game.rounds[-1].votes.values())
|
|
self.sendmsg(self.game.connection, self.game.channel,
|
|
" {0:d} ({1:s}): {2:d}".format(i+1, s, len(votes)))
|
|
i += 1
|
|
|
|
def _add_round_scores_to_game_scores(self):
|
|
"""Apply the final round scores to the totall scores for the game."""
|
|
|
|
for s in self.game.rounds[-1].votes.values():
|
|
votes = filter(lambda x: x == s, self.game.rounds[-1].votes.values())
|
|
if s in self.game.scores.keys():
|
|
self.game.scores[s] = self.game.scores[s] + len(votes)
|
|
else:
|
|
self.game.scores[s] = len(votes)
|
|
|
|
def _continue_or_quit(self):
|
|
"""Decide whether the game should continue or quit."""
|
|
|
|
if self.game.state == 4:
|
|
if self.game.quit == True:
|
|
self._end_game()
|
|
else:
|
|
self._start_new_round()
|
|
|
|
def _end_game(self):
|
|
"""Clean up the entire game."""
|
|
|
|
self.game.state = 0
|
|
self.game.quit = False
|
|
self.sendmsg(self.game.connection, self.game.channel,
|
|
"game's over! here are the final scores:")
|
|
self._print_game_scores()
|
|
|
|
def _print_game_scores(self):
|
|
"""Print the final calculated scores."""
|
|
|
|
for s in self.game.scores.keys():
|
|
self.sendmsg(self.game.connection, self.game.channel,
|
|
" {0:s}: {1:d}".format(s, self.game.scores[s]))
|
|
|
|
# vi:tabstop=4:expandtab:autoindent
|