pyircbot/pyircbot/modules/Election.py

159 lines
5.7 KiB
Python

#!/usr/bin/env python
"""
.. module:: GameBase
:synopsis: A codebase for making IRC games
.. moduleauthor:: Dave Pedu <dave@davepedu.com>
"""
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 <item>' 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