""" 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 . """ import random import re 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 = 60 self.seconds_to_vote = 45 self.seconds_to_submit_step = 10 self.seconds_to_pause = 5 """ Play a game where users come up with silly definitions for randomly-generated acronyms. """ def __init__(self, irc, config): """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) def do(self, connection, event, nick, userhost, what, admin_unlocked): """Handle commands and inputs.""" target = event.target() if self.statusre.search(what): return self.irc.reply(event, self.do_status(what)) if self.startre.search(what): return self.irc.reply(event, self.do_start(connection, target, what)) if self.submitre.search(what): match = self.submitre.search(what) return self.irc.reply(event, self.do_submit(nick, match.group(1))) if self.votere.search(what): match = self.votere.search(what) return self.irc.reply(event, self.do_vote(nick, match.group(1))) if self.quitre.search(what): return self.irc.reply(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 + (self.game.rounds[-1].seconds_to_submit_step * (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: self.log.debug(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): self.log.debug(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