#!/usr/bin/env python """ .. module:: GameBase :synopsis: A codebase for making IRC games .. moduleauthor:: Dave Pedu """ from pyircbot.modulebase import ModuleBase, ModuleHook import random import os import time import math from threading import Timer from collections import namedtuple, defaultdict class Election(ModuleBase): def __init__(self, bot, moduleName): ModuleBase.__init__(self, bot, moduleName) self.hooks = [ModuleHook("PRIVMSG", self.got_msg), ModuleHook("JOIN", self.join_ch)] self.loadConfig() self.games = {} def got_msg(self, event): # Ignore messages from users not logged in if event.args[0][0] == "#": # Channel message self.games[event.args[0]].gotMsg(event) def join_ch(self, event): if event.prefix.nick == self.bot.get_nick(): joined_ch = event.trailing if joined_ch not in self.games: if self.config["channelWhitelistOn"] and joined_ch not in self.config["channelWhitelist"]: return self.games[joined_ch] = ElectionGame(self, joined_ch) def ondisable(self): self.log.info("GameBase: Unload requested, ending games...") for game in self.games: self.games[game].gameover() _game_phases = namedtuple("Phase", "election cooldown halted") game_phases = _game_phases(0, 1, 2) class ElectionGame: def __init__(self, master, channel): self.master = master self.bot = master.bot self.channel = channel self.state = game_phases.election self.election_length_s = self.master.config.get('duration', 30) self.cooldown_length_s = self.master.config.get('cooldown', 30) self.ends_at = time.time() + self.election_length_s self.end_timer = None self.cooldown_timer = None self.start_election() def start_election(self): self.cooldown_timer = None self.votes = defaultdict(list, dict(**{i: [] for i in self.master.config["choices"]})) self.state = game_phases.election self.end_timer = Timer(self.election_length_s, self.end_election) self.end_timer.start() self.bot.act_PRIVMSG(self.channel, "[Election started] - vote with '.vote ' choosing from: {}." " Ends in {}".format(', '.join(self.votes.keys()), self.format_seconds(self.election_length_s))) def end_election(self): self.end_timer = None self.state = game_phases.cooldown self.print_results(prefix="[Election finished] ", show_writeins=True) # Only start a new game if someone voted if sum([len(i) for i in self.votes.values()]) > 0: self.cooldown_timer = Timer(self.cooldown_length_s, self.start_election) self.cooldown_timer.start() self.bot.act_PRIVMSG(self.channel, "Next election starts in: {}".format(self.format_seconds(self.cooldown_length_s))) else: self.bot.act_PRIVMSG(self.channel, "Nobody voted! To turn me on again, type .election") self.state = game_phases.halted def gotMsg(self, event): if self.state == game_phases.halted: cmd = self.master.bot.messageHasCommand(".election", event.trailing) if cmd: self.start_election() if self.state == game_phases.election: cmd = self.master.bot.messageHasCommand(".vote", event.trailing, requireArgs=True) if cmd: if self.master.config.get('allow_writeins', True) or cmd.args[0] in self.votes.keys(): self.annul_voter(event.prefix.nick) self.votes[cmd.args[0]].append(event.prefix.nick) else: # Invalid candidate pass cmd = self.master.bot.messageHasCommand(".votes", event.trailing) if cmd: self.print_results(show_remaining_time=True, show_writeins=True) else: # No election running, pass pass def annul_voter(self, voter_nick): for target in self.votes: try: self.votes[target].remove(voter_nick) except ValueError: pass def print_results(self, prefix='', postfix='', show_remaining_time=False, show_writeins=False): results = ['{} - {}'.format(candidate, len(voters)) for candidate, voters in self.votes.items() if candidate in self.master.config["choices"]] writeins = [(candidate, len(voters)) for candidate, voters in self.votes.items() if candidate not in self.master.config["choices"]] writeins = sorted(writeins, reverse=True, key=lambda item: item[1]) writein_results = ['{} - {}'.format(candidate, voters) for candidate, voters in writeins if voters > 0] togo_info = "" if show_remaining_time: togo_info = " | ends in {}".format(self.format_seconds(self.ends_at - time.time())) self.bot.act_PRIVMSG(self.channel, "{}{}{}{}".format(prefix, ', '.join(results), postfix, togo_info)) if show_writeins and writein_results: self.bot.act_PRIVMSG(self.channel, "Top write-ins: {}".format(', '.join(writein_results[0:5]))) def gameover(self): for t in [self.end_timer, self.cooldown_timer]: if t: t.cancel() def format_seconds(self, secs): """ Turns '90' into '1m30s' """ output = "" minutes = 0 if secs > 60: minutes = math.floor(secs / 60) output += "{}m".format(minutes) secs -= minutes * 60 output += "{}s".format(round(secs)) return output