зеркало из https://github.com/mozilla/gecko-dev.git
Quizbot module. b=124621, r=imajes
This commit is contained in:
Родитель
51c165dbb8
Коммит
80036cfbb1
|
@ -20,4 +20,6 @@ Contributor(s): Harrison Page <harrison@netscape.com>
|
|||
Risto Kotalampi <risto@kotalampi.com>
|
||||
Josh Soref <timeless@mac.com>
|
||||
Ian Hickson <mozbot@hixie.ch>
|
||||
Zach Lipton <zach@zachlipton.com>
|
||||
Zach Lipton <zach@zachlipton.com>
|
||||
Jake Steenhagen <jake@acutex.net>
|
||||
mental <xor@ivwnet.com>
|
|
@ -0,0 +1,90 @@
|
|||
# -*- Mode: perl; tab-width: 4; indent-tabs-mode: nil; -*-
|
||||
################################
|
||||
# General Module #
|
||||
################################
|
||||
|
||||
package BotModules::General;
|
||||
use vars qw(@ISA);
|
||||
@ISA = qw(BotModules);
|
||||
1;
|
||||
|
||||
my $VERSION = '2.1';
|
||||
|
||||
# RegisterConfig - Called when initialised, should call registerVariables
|
||||
sub RegisterConfig {
|
||||
my $self = shift;
|
||||
$self->SUPER::RegisterConfig(@_);
|
||||
$self->registerVariables(
|
||||
# [ name, save?, settable?, value ]
|
||||
['preferredHelpLineLength', 1, 1, 90],
|
||||
);
|
||||
}
|
||||
|
||||
sub Help {
|
||||
my $self = shift;
|
||||
my ($event) = @_;
|
||||
return {
|
||||
'' => 'The module that provides the bot-wide services.',
|
||||
'help' => 'Gives information about modules and commands. Syntax: help [<topic>]',
|
||||
};
|
||||
}
|
||||
|
||||
# Told - Called for messages prefixed by the bot's nick
|
||||
sub Told {
|
||||
my $self = shift;
|
||||
my ($event, $message) = @_;
|
||||
if ($message =~ /^\s*help(?:\s+($variablepattern))?[ ?!.]*\s*$/osi) {
|
||||
if ($1) {
|
||||
# display help for that command
|
||||
# first, build the help file...
|
||||
my %topicList;
|
||||
foreach my $module (@modules) {
|
||||
my $commands = $module->Help($event);
|
||||
if ($commands->{''}) {
|
||||
$topicList{lc($module->{'_name'})} = [] unless defined($topicList{lc($module->{'_name'})});
|
||||
push(@{$topicList{lc($module->{'_name'})}}, $commands->{''});
|
||||
}
|
||||
foreach (keys %$commands) {
|
||||
$topicList{lc($_)} = [] unless defined($topicList{lc($_)});
|
||||
push(@{$topicList{lc($_)}}, $commands->{lc($_)});
|
||||
}
|
||||
}
|
||||
if (defined($topicList{lc($1)})) {
|
||||
foreach (@{$topicList{lc($1)}}) {
|
||||
$self->say($event, "$1: $_");
|
||||
}
|
||||
} else {
|
||||
$self->say($event, "No help for topic '$1'.");
|
||||
}
|
||||
} else {
|
||||
$self->directSay($event, "Help topics for mozbot $VERSION ($helpline):");
|
||||
$self->say($event, "$event->{'from'}: help info /msg'ed") if ($event->{'channel'});
|
||||
local @" = ', '; # to reset font-lock: "
|
||||
my @helplist;
|
||||
foreach my $module (@modules) {
|
||||
my %commands = %{$module->Help($event)};
|
||||
my $moduleHelp = delete($commands{''});
|
||||
my @commands = sort keys %commands;
|
||||
if (@commands) {
|
||||
push(@helplist, "$module->{'_name'}: @commands");
|
||||
} elsif ($moduleHelp) {
|
||||
push(@helplist, "$module->{'_name'}");
|
||||
}
|
||||
}
|
||||
foreach ($self->prettyPrint($self->{'preferredHelpLineLength'}, undef, ' ', '; ', @helplist)) {
|
||||
$self->directSay($event, $_);
|
||||
}
|
||||
$self->directSay($event, 'For help on a particular topic, type \'help <topic>\'. Note that some commands may be disabled in certain channels.');
|
||||
}
|
||||
} else {
|
||||
return $self->SUPER::Told(@_);
|
||||
}
|
||||
return 0; # dealt with it, do nothing else
|
||||
}
|
||||
|
||||
sub CTCPVersion {
|
||||
my $self = shift;
|
||||
my ($event, $who, $what) = @_;
|
||||
local $" = ', ';
|
||||
$self->ctcpReply($event, 'VERSION', "mozbot $VERSION (@modulenames)");
|
||||
}
|
|
@ -0,0 +1,179 @@
|
|||
# -*- Mode: perl; tab-width: 4; indent-tabs-mode: nil; -*-
|
||||
################################
|
||||
# List Module #
|
||||
################################
|
||||
|
||||
package BotModules::List;
|
||||
use vars qw(@ISA);
|
||||
@ISA = qw(BotModules);
|
||||
1;
|
||||
|
||||
# XXX Wipe entire list command
|
||||
|
||||
# RegisterConfig - Called when initialised, should call registerVariables
|
||||
sub RegisterConfig {
|
||||
my $self = shift;
|
||||
$self->SUPER::RegisterConfig(@_);
|
||||
$self->registerVariables(
|
||||
# [ name, save?, settable? ]
|
||||
['lists', 1, 1, {}], # user => 'list name|item 1|item 2||list name|item1|item 2'
|
||||
['preferredLineLength', 1, 1, 80], # the usual
|
||||
['maxItemsInChannel', 1, 1, 20], # max number of items to print in the channel (above this and direct messages are used)
|
||||
);
|
||||
}
|
||||
|
||||
sub Help {
|
||||
my $self = shift;
|
||||
my ($event) = @_;
|
||||
return {
|
||||
'' => 'A personal list tracker. Store your lists here. You must be authenticated to use this (see \'newuser\'). Use the \'add\' command to add items to a list.',
|
||||
'add' => 'Add an item to a personal list. List names shouldn\'t contain the word \'to\' otherwise things will be too ambiguous. Syntax: \'add <thing to add> to <list name> list\', e.g. \'add bug 5693 to critical bug list\'.',
|
||||
'remove' => 'Remove an item from a personal list. Syntax: \'remove <thing to add> from <list name> list\', e.g. \'remove bug 5693 from critical bug list\'.',
|
||||
'list' => 'List the items in your list. Syntax: \'list items in <name of list> list\', e.g. \'list items in critical bug list\' or just \'critical bug list\'.',
|
||||
'lists' => 'Tells you what lists you have set up.',
|
||||
};
|
||||
}
|
||||
|
||||
sub Told {
|
||||
my $self = shift;
|
||||
my ($event, $message) = @_;
|
||||
if ($message =~ /^\s*add\s+(\S(?:.*\S)?)\s+to\s+(?:my\s+)?(\S(?:.*\S)?)\s+list[\s!.]*$/osi and $message !~ /\|/o and $event->{'userName'}) {
|
||||
$self->AddItem($event, $1, $2);
|
||||
} elsif ($message =~ /^\s*remove\s+(\S(?:.*\S)?)\s+from\s+(?:my\s+)?(\S(?:.*\S)?)\s+list[\s!.]*$/osi and $message !~ /\|/o and $event->{'userName'}) {
|
||||
$self->RemoveItem($event, $1, $2);
|
||||
} elsif ($message =~ /^\s* (?:examine \s+ |
|
||||
list \s+ items \s+ in \s+ |
|
||||
what (?:\s+is|'s) \s+ (?:in\s+)? )
|
||||
(?: my \s+ | the \s+ )?
|
||||
( \S (?:.*\S)? )
|
||||
\s+ list [\s!?.]* $/osix
|
||||
and $message !~ /\|/o and $event->{'userName'}) {
|
||||
$self->ListItems($event, $1);
|
||||
} elsif ($message =~ /^\s*lists[?\s.!]*$/osi and $event->{'userName'}) {
|
||||
$self->ListLists($event, $1);
|
||||
} else {
|
||||
return $self->SUPER::Told(@_);
|
||||
}
|
||||
return 0; # dealt with it...
|
||||
}
|
||||
|
||||
sub Baffled {
|
||||
my $self = shift;
|
||||
my ($event, $message) = @_;
|
||||
if ($message =~ /^\s*(\S(?:.*\S)?)\s+list[\s!?.]*$/osi and $message !~ /\|/o and $event->{'userName'}) {
|
||||
$self->ListItems($event, $1);
|
||||
} else {
|
||||
return $self->SUPER::Baffled(@_);
|
||||
}
|
||||
return 0; # dealt with it...
|
||||
}
|
||||
|
||||
sub Heard {
|
||||
my $self = shift;
|
||||
my ($event, $message) = @_;
|
||||
if ($message =~ /^\s*add\s+(\S(?:.*\S)?)\s+to\s+(?:my\s+)?(\S(?:.*\S)?)\s+list[\s!.]*$/osi and $message !~ /\|/o and $event->{'userName'}) {
|
||||
$self->AddItem($event, $1, $2);
|
||||
} elsif ($message =~ /^\s*remove\s+(\S(?:.*\S)?)\s+from\s+(?:my\s+)?(\S(?:.*\S)?)\s+list[\s!.]*$/osi and $message !~ /\|/o and $event->{'userName'}) {
|
||||
$self->RemoveItem($event, $1, $2);
|
||||
} else {
|
||||
return $self->SUPER::Told(@_);
|
||||
}
|
||||
return 0; # dealt with it...
|
||||
}
|
||||
|
||||
sub AddItem {
|
||||
my $self = shift;
|
||||
my ($event, $what, $list) = @_;
|
||||
my @lists = split(/\|\|/o, $self->{'lists'}->{$event->{'userName'}});
|
||||
local $" = '\', \'';
|
||||
my %lists;
|
||||
foreach my $sublist (@lists) {
|
||||
my @items = split(/\|/o, $sublist);
|
||||
$lists{shift @items} = \@items;
|
||||
}
|
||||
push(@{$lists{lc $list}}, $what);
|
||||
local $" = '|';
|
||||
my $compoundLists = '';
|
||||
foreach my $list (keys(%lists)) {
|
||||
if ($compoundLists ne '') {
|
||||
$compoundLists .= '||';
|
||||
}
|
||||
$compoundLists .= "$list|@{$lists{$list}}";
|
||||
}
|
||||
$self->{'lists'}->{$event->{'userName'}} = $compoundLists;
|
||||
$self->saveConfig();
|
||||
$self->say($event, "$event->{'from'}: stored '$what' in '$list' list");
|
||||
}
|
||||
|
||||
sub RemoveItem {
|
||||
my $self = shift;
|
||||
my ($event, $what, $list) = @_;
|
||||
my @lists = split(/\|\|/o, $self->{'lists'}->{$event->{'userName'}});
|
||||
local $" = '\', \'';
|
||||
my %lists;
|
||||
my $removed = 0;
|
||||
foreach my $sublist (@lists) {
|
||||
my @items = split(/\|/o, $sublist);
|
||||
if (lc $list eq $items[0]) {
|
||||
my $listName = shift @items;
|
||||
foreach my $item (@items) {
|
||||
if (lc $what ne lc $item) {
|
||||
push(@{$lists{$listName}}, $item);
|
||||
} else {
|
||||
$removed++;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$lists{shift @items} = \@items;
|
||||
}
|
||||
}
|
||||
local $" = '|';
|
||||
my $compoundLists = '';
|
||||
foreach my $list (keys(%lists)) {
|
||||
if ($compoundLists ne '') {
|
||||
$compoundLists .= '||';
|
||||
}
|
||||
$compoundLists .= "$list|@{$lists{$list}}";
|
||||
}
|
||||
$self->{'lists'}->{$event->{'userName'}} = $compoundLists;
|
||||
$self->saveConfig();
|
||||
if ($removed) {
|
||||
$self->say($event, "$event->{'from'}: removed '$what' from '$list' list");
|
||||
} else {
|
||||
$self->say($event, "$event->{'from'}: could not find '$what' in '$list' list");
|
||||
}
|
||||
}
|
||||
|
||||
sub ListItems {
|
||||
my $self = shift;
|
||||
my ($event, $list) = @_;
|
||||
my @lists = split(/\|\|/o, $self->{'lists'}->{$event->{'userName'}});
|
||||
my %lists;
|
||||
foreach my $list (@lists) {
|
||||
my @items = split(/\|/o, $list);
|
||||
$lists{lc shift @items} = \@items;
|
||||
}
|
||||
if (defined(@{$lists{lc $list}})) {
|
||||
my $size = scalar(@{$lists{lc $list}});
|
||||
if ($size > $self->{'maxItemsInChannel'}) {
|
||||
$self->channelSay($event, "$event->{'from'}: Your $list list contains $size items, which I am /msg'ing you.");
|
||||
$self->directSay($event, $self->prettyPrint($self->{'preferredLineLength'}, "Your $list list contains: ", '', ', ', @{$lists{lc $list}}));
|
||||
} else {
|
||||
$self->say($event, $self->prettyPrint($self->{'preferredLineLength'}, "Your $list list contains: ", $event->{'channel'} eq '' ? '' : "$event->{'from'}: ", ', ', @{$lists{lc $list}}));
|
||||
}
|
||||
} else {
|
||||
$self->say($event, "You don't have a $list list, sorry.");
|
||||
}
|
||||
}
|
||||
|
||||
sub ListLists {
|
||||
my $self = shift;
|
||||
my ($event) = @_;
|
||||
my @lists = split(/\|\|/o, $self->{'lists'}->{$event->{'userName'}});
|
||||
my @listNames;
|
||||
foreach my $list (@lists) {
|
||||
my @items = split(/\|/o, $list);
|
||||
push(@listNames, $items[0]);
|
||||
}
|
||||
$self->say($event, $self->prettyPrint($self->{'preferredLineLength'}, "Your lists are: ", $event->{'channel'} eq '' ? '' : "$event->{'from'}: ", ', ', @listNames));
|
||||
}
|
|
@ -0,0 +1,572 @@
|
|||
# -*- Mode: perl; tab-width: 4; indent-tabs-mode: nil; -*-
|
||||
################################
|
||||
# Quiz Module #
|
||||
################################
|
||||
# some of these ideas are stolen from moxquizz (an eggdrop module)
|
||||
# see http://www.meta-x.de/moxquizz/
|
||||
|
||||
package BotModules::Quiz;
|
||||
use vars qw(@ISA);
|
||||
@ISA = qw(BotModules);
|
||||
1;
|
||||
|
||||
# XXX high score table
|
||||
# XXX do something with level
|
||||
# XXX make bot able to self-abort if no-one is taking part
|
||||
# XXX implement feature so that users that can be quiz admins in certain channels
|
||||
# XXX accept user submission
|
||||
# XXX README for database format (for now see http://www.meta-x.de/moxquizz/README.database)
|
||||
# XXX pause doesn't stop count of how long answer takes to answer
|
||||
# XXX different quiz formats, e.g. university challenge, weakest link (maybe implement by inheritance?)
|
||||
# XXX stats, e.g. number of questions skipped
|
||||
# XXX category filtering
|
||||
|
||||
sub Help {
|
||||
my $self = shift;
|
||||
my($event) = @_;
|
||||
my $help = {
|
||||
'' => "Runs quizzes. Start a quiz with the $self->{'prefix'}ask command.",
|
||||
$self->{'prefix'}.'ask' => 'Starts a quiz.',
|
||||
$self->{'prefix'}.'pause' => "Pauses the current quiz. Resume with $self->{'prefix'}resume.",
|
||||
$self->{'prefix'}.'resume' => 'Resumes the current quiz.',
|
||||
$self->{'prefix'}.'repeat' => 'Repeats the current question.',
|
||||
$self->{'prefix'}.'endquiz' => 'Ends the current quiz.',
|
||||
$self->{'prefix'}.'next' => 'Jump to the next question (at least half of the active participants have to say this for the question to be skipped).',
|
||||
$self->{'prefix'}.'score' => 'Show the current scores for the round.',
|
||||
};
|
||||
if ($self->isAdmin($event)) {
|
||||
$help->{'reload'} = 'To just reload the quiz data files instead of the whole module, use: reload Quiz Data';
|
||||
}
|
||||
return $help;
|
||||
}
|
||||
|
||||
# RegisterConfig - Called when initialised, should call registerVariables
|
||||
sub RegisterConfig {
|
||||
my $self = shift;
|
||||
$self->SUPER::RegisterConfig(@_);
|
||||
$self->registerVariables(
|
||||
# [ name, save?, settable? ]
|
||||
['questionSets', 1, 1, ['trivia.en']], # the list of files to read (from the Quiz/ directory)
|
||||
['questions', 0, 0, []], # the list of questions (hashes)
|
||||
['categories', 0, 0, {}], # hash of arrays whose values are indexes into questions
|
||||
['questionsPerRound', 1, 1, -1], # how many questions per round (-1 = infinite)
|
||||
['currentQuestion', 1, 0, {}], # the active question (per-channel hash)
|
||||
['questionIndex', 1, 0, 0], # where to start when picking the next question
|
||||
['skipMargin', 1, 1, 10], # maximum number of questions to skip at a time
|
||||
['remainingQuestions', 1, 0, {}], # how many more questions this round (per-channel hash)
|
||||
['questionsTime', 1, 0, {}], # when the question was asked
|
||||
['quizTime', 1, 0, {}], # when the quiz was started
|
||||
['paused', 1, 0, {}], # if the game is paused
|
||||
['totalScores', 1, 1, {}], # user => score
|
||||
['quizScores', 1, 0, {}], # channel => "user score"
|
||||
['skip', 1, 0, {}], # channel => "user 1"
|
||||
['players', 1, 0, {}], # channel => "user last time"
|
||||
['tip', 1, 0, {}], # which tip should next be given on this channel
|
||||
['tipDelay', 1, 1, 10], # seconds to wait before giving a tip
|
||||
['timeout', 1, 1, 120], # seconds to wait before giving up
|
||||
['skipFractionRequired', 1, 1, 0.5], # fraction of players that must say !skip to skip
|
||||
['askDelay', 1, 1, 2], # how long to wait between answer and question
|
||||
['prefix', 1, 1, '!'], # the prefix to have at the start of commands
|
||||
);
|
||||
}
|
||||
|
||||
sub Schedule {
|
||||
my $self = shift;
|
||||
my($event) = @_;
|
||||
$self->reloadData($event);
|
||||
my $fakeEvent = {%$event};
|
||||
foreach my $channel (keys %{$self->{'currentQuestion'}}) {
|
||||
$fakeEvent->{'channel'} = $channel;
|
||||
$fakeEvent->{'target'} = $channel;
|
||||
$self->debug("Restarting quiz in $channel... (qid $self->{'questionsTime'}->{$channel})");
|
||||
$self->schedule($fakeEvent, \$self->{'tipDelay'}, 1, 'tip', $self->{'questionsTime'}->{$channel});
|
||||
$self->schedule($fakeEvent, \$self->{'timeout'}, 1, 'timeout', $self->{'questionsTime'}->{$channel});
|
||||
if ($self->{'questionsTime'}->{$event->{'channel'}} == 0) {
|
||||
$self->schedule($event, \$self->{'askDelay'}, 1, 'ask');
|
||||
}
|
||||
}
|
||||
$self->SUPER::Schedule($event);
|
||||
}
|
||||
|
||||
sub Told {
|
||||
my $self = shift;
|
||||
my($event, $message) = @_;
|
||||
if ($message =~ /^\s*reload\s+quiz\s+data\s*$/osi and $self->isAdmin($event)) {
|
||||
my $count = $self->reloadData($event);
|
||||
$self->say($event, "$count questions loaded");
|
||||
} elsif ($message =~ /^\s*status[?\s]*$/osi) {
|
||||
my $questions = @{$self->{'questions'}};
|
||||
my $quizzes = keys %{$self->{'currentQuestion'}};
|
||||
my $prefix = '';
|
||||
if ($event->{'channel'} ne '') {
|
||||
$prefix = "$event->{'from'}: ";
|
||||
}
|
||||
$self->say($event, $prefix."I have $questions questions and am running $quizzes quizzes.", 1); # XXX 1 quizzes
|
||||
} elsif (not $self->DoQuizCheck($event, $message, 1)) {
|
||||
return $self->SUPER::Told(@_);
|
||||
}
|
||||
return 0; # we've dealt with it, no need to do anything else.
|
||||
}
|
||||
|
||||
sub Baffled {
|
||||
my $self = shift;
|
||||
my($event, $message) = @_;
|
||||
if (not $self->quizAnswer($event, $message)) {
|
||||
return $self->SUPER::Baffled(@_);
|
||||
}
|
||||
return 0; # we've dealt with it, no need to do anything else.
|
||||
}
|
||||
|
||||
sub Heard {
|
||||
my $self = shift;
|
||||
my($event, $message) = @_;
|
||||
if (not $self->DoQuizCheck($event, $message, 0) and
|
||||
not $self->quizAnswer($event, $message)) {
|
||||
return $self->SUPER::Heard(@_);
|
||||
}
|
||||
return 0; # we've dealt with it, no need to do anything else.
|
||||
}
|
||||
|
||||
sub DoQuizCheck {
|
||||
my $self = shift;
|
||||
my($event, $message, $direct) = @_;
|
||||
if ($message =~ /^\s*\Q$self->{'prefix'}\Eask\s*$/si) {
|
||||
$self->quizStart($event);
|
||||
} elsif ($message =~ /^\s*\Q$self->{'prefix'}\Epause\s*$/si) {
|
||||
$self->quizPause($event);
|
||||
} elsif ($message =~ /^\s*\Q$self->{'prefix'}\E(?:resume|unpause)\s*$/si) {
|
||||
$self->quizResume($event);
|
||||
} elsif ($message =~ /^\s*\Q$self->{'prefix'}\Erepeat\s*$/si) {
|
||||
$self->quizRepeat($event);
|
||||
} elsif ($message =~ /^\s*\Q$self->{'prefix'}\E(?:end|stop|strivia|exit)(?:quiz)?\s*$/si) {
|
||||
$self->quizEnd($event);
|
||||
} elsif ($message =~ /^\s*\Q$self->{'prefix'}\E(?:dunno|skip|next)\s*$/si) {
|
||||
$self->quizSkip($event);
|
||||
} elsif ($message =~ /^\s*\Q$self->{'prefix'}\E(?:scores)\s*$/si) {
|
||||
$self->quizScores($event);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub reloadData {
|
||||
my $self = shift;
|
||||
my($event) = @_;
|
||||
$self->{'questions'} = [];
|
||||
$self->{'categories'} = {};
|
||||
$self->debug('Loading quiz data...');
|
||||
foreach my $set (@{$self->{'questionSets'}}) {
|
||||
if ($set =~ m/^[a-zA-Z0-9-][a-zA-Z0-9.-]*$/os) {
|
||||
local *FILE;
|
||||
if (not open(FILE, "<BotModules/Quiz/$set")) { # XXX what if the directory has changed?
|
||||
$self->debug(" * $set (Not loaded; $!)");
|
||||
next;
|
||||
}
|
||||
$self->debug(" * $set");
|
||||
my $category;
|
||||
my $question = {'tip' => []};
|
||||
while (defined($_ = <FILE>)) {
|
||||
chomp;
|
||||
next if m/^\#/os; # skip comment lines
|
||||
next if m/^\s*$/os; # skip blank lines
|
||||
if (m/^Category:\s*(.*?)\s*$/os) {
|
||||
# Category? (should always be on top!)
|
||||
$category = $1;
|
||||
if (not defined($self->{'categories'}->{$category})) {
|
||||
$self->{'categories'}->{$category} = [];
|
||||
}
|
||||
} elsif (m/^Question:\s*(.*?)\s*$/os) {
|
||||
# Question (should always stand after Category)
|
||||
$question = {'question' => $1, 'tip' => []};
|
||||
if (defined($category)) {
|
||||
$question->{'category'} = $category;
|
||||
undef($category);
|
||||
}
|
||||
push(@{$self->{'questions'}}, $question);
|
||||
push(@{$self->{'categories'}->{$category}}, $#{$self->{'questions'}});
|
||||
} elsif (m/^Answer:\s*(?:(.*?)\#(.*?)\#(.*?)|(.*?))\s*$/os) {
|
||||
# Answer (will be matched if no regexp is provided)
|
||||
if (defined($1)) {
|
||||
$question->{'answer-long'} = "$1$2$3";
|
||||
$question->{'answer-short'} = $2;
|
||||
} else {
|
||||
$question->{'answer-long'} = $4;
|
||||
$question->{'answer-short'} = $4;
|
||||
}
|
||||
} elsif (m/^Regexp:\s*(.*?)\s*$/os) {
|
||||
# Regexp? (use UNIX-style expressions)
|
||||
$question->{'answer-regexp'} = $1;
|
||||
} elsif (m/^Author:\s*(.*?)\s*$/os) {
|
||||
# Author? (the brain behind this question)
|
||||
$question->{'author'} = $1;
|
||||
} elsif (m/^Level:\s*(.*?)\s*$/os) {
|
||||
# Level? [baby|easy|normal|hard|extreme] (difficulty)
|
||||
$question->{'level'} = $1;
|
||||
} elsif (m/^Comment:\s*(.*?)\s*$/os) {
|
||||
# Comment? (comment line)
|
||||
$question->{'comment'} = $1;
|
||||
} elsif (m/^Score:\s*(.*?)\s*$/os) {
|
||||
# Score? [#] (credits for answering this question)
|
||||
$question->{'score'} = $1;
|
||||
} elsif (m/^Tip:\s*(.*?)\s*$/os) {
|
||||
# Tip* (provide one or more hints)
|
||||
push(@{$question->{'tip'}}, $1);
|
||||
} elsif (m/^TipCycle:\s*(.*?)\s*$/os) {
|
||||
# TipCycle? [#] (Specify number of generated tips)
|
||||
$question->{'tip-cycle'} = $1;
|
||||
} else {
|
||||
# XXX error handling
|
||||
}
|
||||
}
|
||||
close(FILE);
|
||||
} # else XXX invalid filename, ignore it
|
||||
}
|
||||
# if no more questions, abort running quizes.
|
||||
if (not @{$self->{'questions'}}) {
|
||||
foreach my $channel (keys %{$self->{'currentQuestion'}}) {
|
||||
local $event->{'channel'} = $channel;
|
||||
$self->say($event, 'There are no more questions.');
|
||||
$self->quizEnd($event);
|
||||
}
|
||||
}
|
||||
return scalar(@{$self->{'questions'}});
|
||||
}
|
||||
|
||||
|
||||
# game implementation
|
||||
|
||||
sub Scheduled {
|
||||
my $self = shift;
|
||||
my($event, @data) = @_;
|
||||
if ($data[0] eq 'tip') {
|
||||
if ($self->{'questionsTime'}->{$event->{'channel'}} == $data[1] and
|
||||
defined($self->{'currentQuestion'}->{$event->{'channel'}})) {
|
||||
# $self->debug('time for a tip');
|
||||
if ($self->{'paused'}->{$event->{'channel'}} or
|
||||
$self->quizTip($event)) {
|
||||
$self->schedule($event, \$self->{'tipDelay'}, 1, @data);
|
||||
}
|
||||
}
|
||||
} elsif ($data[0] eq 'timeout') {
|
||||
if ($self->{'questionsTime'}->{$event->{'channel'}} == $data[1] and
|
||||
defined($self->{'currentQuestion'}->{$event->{'channel'}})) {
|
||||
if ($self->{'paused'}->{$event->{'channel'}}) {
|
||||
$self->schedule($event, \$self->{'timeout'}, 1, @data);
|
||||
} else {
|
||||
my $answer = $self->{'questions'}->[$self->{'currentQuestion'}->{$event->{'channel'}}]->{'answer-long'};
|
||||
$self->say($event, "Too late! The answer was: $answer");
|
||||
$self->quizQuestion($event);
|
||||
}
|
||||
}
|
||||
} elsif ($data[0] eq 'ask') {
|
||||
if (defined($self->{'currentQuestion'}->{$event->{'channel'}})) {
|
||||
$self->quizQuestion($event);
|
||||
}
|
||||
} else {
|
||||
$self->SUPER::Scheduled($event, @data);
|
||||
}
|
||||
}
|
||||
|
||||
sub quizStart { # called by user
|
||||
my $self = shift;
|
||||
my($event) = @_;
|
||||
if ($event->{'channel'} ne '' and
|
||||
not defined($self->{'currentQuestion'}->{$event->{'channel'}})) {
|
||||
if (@{$self->{'questions'}} == 0) {
|
||||
# if no questions, complain.
|
||||
$self->say($event, 'I cannot run a quiz with no questions!');
|
||||
} else {
|
||||
# no game in progress, start one
|
||||
$self->{'remainingQuestions'}->{$event->{'channel'}} = $self->{'questionsPerRound'};
|
||||
$self->{'paused'}->{$event->{'channel'}} = 0;
|
||||
$self->{'quizTime'}->{$event->{'channel'}} = time();
|
||||
$self->{'quizScores'}->{$event->{'channel'}} = '';
|
||||
$self->{'players'}->{$event->{'channel'}} = '';
|
||||
$self->quizQuestion($event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub quizQuestion { # called from quizStart or delayed from quizAnswer
|
||||
my $self = shift;
|
||||
my($event) = @_;
|
||||
if ($event->{'channel'} ne '' and # in channel
|
||||
not $self->{'paused'}->{$event->{'channel'}}) { # quiz not paused
|
||||
if ($self->{'remainingQuestions'}->{$event->{'channel'}} != 0) {
|
||||
$self->{'remainingQuestions'}->{$event->{'channel'}}--;
|
||||
my $category = $self->{'questions'}->[$self->{'currentQuestion'}->{$event->{'channel'}}]->{'category'};
|
||||
while ($category eq $self->{'questions'}->[$self->{'currentQuestion'}->{$event->{'channel'}}]->{'category'}
|
||||
and my $try++ < keys %{$self->{'questions'}}) {
|
||||
$self->{'currentQuestion'}->{$event->{'channel'}} = $self->pickQuestion();
|
||||
}
|
||||
$self->{'questionsTime'}->{$event->{'channel'}} = time();
|
||||
$self->{'tip'}->{$event->{'channel'}} = 0;
|
||||
$self->{'skip'}->{$event->{'channel'}} = '';
|
||||
$self->schedule($event, \$self->{'tipDelay'}, 1, 'tip', $self->{'questionsTime'}->{$event->{'channel'}});
|
||||
$self->schedule($event, \$self->{'timeout'}, 1, 'timeout', $self->{'questionsTime'}->{$event->{'channel'}});
|
||||
$self->say($event, "Question: $self->{'questions'}->[$self->{'currentQuestion'}->{$event->{'channel'}}]->{'question'}");
|
||||
$self->debug("Question: $self->{'questions'}->[$self->{'currentQuestion'}->{$event->{'channel'}}]->{'question'}");
|
||||
$self->debug("Answer: $self->{'questions'}->[$self->{'currentQuestion'}->{$event->{'channel'}}]->{'answer-long'}");
|
||||
$self->saveConfig();
|
||||
} else {
|
||||
$self->quizEnd($event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub quizAnswer { # called by user
|
||||
my $self = shift;
|
||||
my($event, $message) = @_;
|
||||
if ($event->{'channel'} ne '' and # in channel
|
||||
defined($self->{'currentQuestion'}->{$event->{'channel'}}) and # in quiz
|
||||
$self->{'questionsTime'}->{$event->{'channel'}} and # not answered
|
||||
not $self->{'paused'}->{$event->{'channel'}}) { # quiz not paused
|
||||
$self->stringHash(\$self->{'players'}->{$event->{'channel'}}, $event->{'from'}, time());
|
||||
if (lc($message) eq lc($self->{'questions'}->[$self->{'currentQuestion'}->{$event->{'channel'}}]->{'answer-long'}) or
|
||||
(defined($self->{'questions'}->[$self->{'currentQuestion'}->{$event->{'channel'}}]->{'answer-short'}) and
|
||||
lc($message) eq lc($self->{'questions'}->[$self->{'currentQuestion'}->{$event->{'channel'}}]->{'answer-short'})) or
|
||||
(defined($self->{'questions'}->[$self->{'currentQuestion'}->{$event->{'channel'}}]->{'answer-regexp'}) and
|
||||
$message =~ /$self->{'questions'}->[$self->{'currentQuestion'}->{$event->{'channel'}}]->{'answer-regexp'}/si)) {
|
||||
# they got it right
|
||||
my $who = $event->{'from'};
|
||||
my $answer = $self->{'questions'}->[$self->{'currentQuestion'}->{$event->{'channel'}}]->{'answer-long'};
|
||||
my $score = $self->{'questions'}->[$self->{'currentQuestion'}->{$event->{'channel'}}]->{'score'};
|
||||
if (not defined($score)) {
|
||||
$score = 1; # use difficulty XXX
|
||||
}
|
||||
my $time = time() - $self->{'questionsTime'}->{$event->{'channel'}};
|
||||
my $total = $self->score($event, $who, $score);
|
||||
$self->debug("Answered by: $who");
|
||||
$self->say($event, "$who got the right answer in $time seconds (+$score points giving $total). The answer was: $answer");
|
||||
$self->saveConfig();
|
||||
$self->{'questionsTime'}->{$event->{'channel'}} = 0;
|
||||
$self->schedule($event, \$self->{'askDelay'}, 1, 'ask');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub quizTip { # called by timer, only during game
|
||||
my $self = shift;
|
||||
my($event) = @_;
|
||||
my $tip;
|
||||
if (defined($self->{'questions'}->[$self->{'currentQuestion'}->{$event->{'channel'}}]->{'tips'}) and
|
||||
$self->{'tip'}->{$event->{'channel'}} < @{$self->{'questions'}->[$self->{'currentQuestion'}->{$event->{'channel'}}]->{'tips'}}) {
|
||||
$tip = $self->{'questions'}->[$self->{'currentQuestion'}->{$event->{'channel'}}]->{'tips'}->[$self->{'tip'}->{$event->{'channel'}}];
|
||||
} else {
|
||||
if (not defined($self->{'questions'}->[$self->{'currentQuestion'}->{$event->{'channel'}}]->{'tips'}) and
|
||||
(not defined($self->{'questions'}->[$self->{'currentQuestion'}->{$event->{'channel'}}]->{'tipCycle'}) or
|
||||
$self->{'tip'}->{$event->{'channel'}} < $self->{'questions'}->[$self->{'currentQuestion'}->{$event->{'channel'}}]->{'tipCycle'})) {
|
||||
$tip = $self->generateTip($self->{'questions'}->[$self->{'currentQuestion'}->{$event->{'channel'}}]->{'answer-long'},
|
||||
$self->{'tip'}->{$event->{'channel'}},
|
||||
$self->{'questions'}->[$self->{'currentQuestion'}->{$event->{'channel'}}]->{'tipCycle'});
|
||||
}
|
||||
}
|
||||
if (defined($tip)) {
|
||||
$self->{'tip'}->{$event->{'channel'}} += 1;
|
||||
$self->say($event, "Hint: $tip...");
|
||||
$self->saveConfig();
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
sub quizPause { # called by user
|
||||
my $self = shift;
|
||||
my($event) = @_;
|
||||
if (defined($self->{'currentQuestion'}->{$event->{'channel'}})) { # game in progress
|
||||
if (not $self->{'paused'}->{$event->{'channel'}}) { # not paused
|
||||
# pause game
|
||||
$self->{'paused'}->{$event->{'channel'}} = 1;
|
||||
$self->saveConfig();
|
||||
$self->say($event, "Quiz paused. Use $self->{'prefix'}resume to continue.");
|
||||
} else {
|
||||
$self->say($event, "Quiz already paused. Use $self->{'prefix'}resume to continue.");
|
||||
}
|
||||
} else {
|
||||
$self->say($event, "No quiz in progress, use $self->{'prefix'}ask to start one.");
|
||||
}
|
||||
}
|
||||
|
||||
sub quizResume { # called by user
|
||||
my $self = shift;
|
||||
my($event) = @_;
|
||||
if (defined($self->{'currentQuestion'}->{$event->{'channel'}})) { # game in progress
|
||||
if ($self->{'paused'}->{$event->{'channel'}}) { # paused
|
||||
# unpause game
|
||||
$self->{'paused'}->{$event->{'channel'}} = 0;
|
||||
$self->saveConfig();
|
||||
$self->say($event, "Quiz resumed. Question: $self->{'questions'}->[$self->{'currentQuestion'}->{$event->{'channel'}}]->{'question'}");
|
||||
} else {
|
||||
$self->say($event, "Quiz already in progress. Use $self->{'prefix'}repeat to be told the question again, and $self->{'prefix'}pause to pause the quiz.");
|
||||
}
|
||||
} else {
|
||||
$self->say($event, "No quiz in progress, use $self->{'prefix'}ask to start one.");
|
||||
}
|
||||
}
|
||||
|
||||
sub quizRepeat { # called by user
|
||||
my $self = shift;
|
||||
my($event) = @_;
|
||||
if (defined($self->{'currentQuestion'}->{$event->{'channel'}})) { # game in progress
|
||||
$self->say($event, "Question: $self->{'questions'}->[$self->{'currentQuestion'}->{$event->{'channel'}}]->{'question'}");
|
||||
} else {
|
||||
$self->say($event, "No quiz in progress, use $self->{'prefix'}ask to start one.");
|
||||
}
|
||||
}
|
||||
|
||||
sub quizEnd { # called by question and user
|
||||
my $self = shift;
|
||||
my($event) = @_;
|
||||
if (defined($self->{'currentQuestion'}->{$event->{'channel'}})) {
|
||||
# get the scores for each player that player in the game
|
||||
my @scores = $self->getScores($event, sub {
|
||||
my($event, $score) = @_;
|
||||
# XXX this means that a user has to be there till the end
|
||||
# of the game to get points added to his high score table.
|
||||
# XXX it also means a user can get better simply by
|
||||
# playing more games.
|
||||
$self->{'totalScores'}->{$score->[1]} += $score->[2];
|
||||
});
|
||||
# print them
|
||||
if (@scores) {
|
||||
local $" = ', ';
|
||||
$self->say($event, "Quiz Ended. Scores: @scores");
|
||||
} else {
|
||||
$self->say($event, 'Quiz Ended. No questions were answered.');
|
||||
}
|
||||
delete($self->{'currentQuestion'}->{$event->{'channel'}});
|
||||
$self->saveConfig();
|
||||
}
|
||||
}
|
||||
|
||||
sub quizScores { # called by user
|
||||
my $self = shift;
|
||||
my($event) = @_;
|
||||
if (defined($self->{'currentQuestion'}->{$event->{'channel'}})) {
|
||||
# get the scores for each player that player in the game
|
||||
my @scores = $self->getScores($event, sub {});
|
||||
# get other stats
|
||||
my $remaining = '';
|
||||
if ($self->{'remainingQuestions'}->{$event->{'channel'}} > 0) {
|
||||
$remaining = " There are $self->{'remainingQuestions'}->{$event->{'channel'}} more questions to go.";
|
||||
}
|
||||
# print them
|
||||
if (@scores) {
|
||||
local $" = ', ';
|
||||
$self->say($event, "Current Scores: @scores$remaining");
|
||||
} else {
|
||||
$self->say($event, "No questions have been answered yet.$remaining");
|
||||
}
|
||||
} else {
|
||||
$self->say($event, "No quiz in progress, use $self->{'prefix'}ask to start one.");
|
||||
}
|
||||
}
|
||||
|
||||
sub quizSkip { # called by user
|
||||
my $self = shift;
|
||||
my($event) = @_;
|
||||
if (defined($self->{'currentQuestion'}->{$event->{'channel'}})) { # game in progress
|
||||
if (not $self->{'paused'}->{$event->{'channel'}}) { # not paused
|
||||
if ($self->{'questionsTime'}->{$event->{'channel'}}) { # question asked and not answered
|
||||
# XXX should only let players skip (at the moment even someone who has not tried to answer any question can skip)
|
||||
# Get number of users who have said !skip (and set current user)
|
||||
my(undef, $skipCount) = $self->stringHash(\$self->{'skip'}->{$event->{'channel'}}, $event->{'from'}, 1);
|
||||
# Get number of users who are playing
|
||||
my $playerCount = $self->getActivePlayers($event);
|
||||
if ($skipCount >= $playerCount * $self->{'skipFractionRequired'}) {
|
||||
my $answer = $self->{'questions'}->[$self->{'currentQuestion'}->{$event->{'channel'}}]->{'answer-long'};
|
||||
$self->say($event, "$skipCount players wanted to skip. Moving to next question. The answer was: $answer");
|
||||
$self->quizQuestion($event);
|
||||
}
|
||||
} # else drop it
|
||||
} else {
|
||||
$self->say($event, "Quiz paused. Use $self->{'prefix'}resume to continue the quiz.");
|
||||
}
|
||||
} else {
|
||||
$self->say($event, "No quiz in progress, use $self->{'prefix'}ask to start one.");
|
||||
}
|
||||
}
|
||||
|
||||
sub pickQuestion {
|
||||
my $self = shift;
|
||||
$self->{'questionIndex'} += 1 + time() % $self->{'skipMargin'};
|
||||
$self->{'questionIndex'} %= @{$self->{'questions'}};
|
||||
return $self->{'questionIndex'};
|
||||
}
|
||||
|
||||
sub score {
|
||||
my $self = shift;
|
||||
my($event, $who, $score) = @_;
|
||||
if (defined($self->{'currentQuestion'}->{$event->{'channel'}})) {
|
||||
my($score, undef) = $self->stringHash(\$self->{'quizScores'}->{$event->{'channel'}}, $who, $score, 1);
|
||||
$self->saveConfig();
|
||||
return $score;
|
||||
}
|
||||
}
|
||||
|
||||
sub getScores {
|
||||
my $self = shift;
|
||||
my($event, $perUser) = @_;
|
||||
my @scores;
|
||||
foreach my $player ($self->getActivePlayers($event)) {
|
||||
my($score, undef) = $self->stringHash(\$self->{'quizScores'}->{$event->{'channel'}}, $player);
|
||||
if (defined($score)) {
|
||||
push(@scores, ["$player: $score", $player, $score]);
|
||||
}
|
||||
}
|
||||
# sort the scores by number
|
||||
@scores = sort {$a->[2] <=> $b->[2]} @scores;
|
||||
foreach my $score (@scores) {
|
||||
&$perUser($event, $score);
|
||||
$score = $score->[0];
|
||||
}
|
||||
return @scores;
|
||||
}
|
||||
|
||||
sub generateTip {
|
||||
my $self = shift;
|
||||
my($answer, $tipID, $maxTips) = @_;
|
||||
if (length($answer) > $tipID+1) {
|
||||
return substr($answer, 0, $tipID+1);
|
||||
} else {
|
||||
return undef;
|
||||
}
|
||||
}
|
||||
|
||||
sub getActivePlayers {
|
||||
my $self = shift;
|
||||
my($event) = @_;
|
||||
my @players;
|
||||
if (defined($self->{'currentQuestion'}->{$event->{'channel'}})) { # game in progress
|
||||
my $start = $self->{'quizTime'}->{$event->{'channel'}};
|
||||
my %players = split(' ', $self->{'players'}->{$event->{'channel'}});
|
||||
foreach my $player (keys %players) {
|
||||
if ($players{$player} > $start) {
|
||||
push(@players, $player);
|
||||
}
|
||||
}
|
||||
}
|
||||
return @players;
|
||||
}
|
||||
|
||||
sub stringHash {
|
||||
my $self = shift;
|
||||
my($string, $key, $value, $multiple) = @_;
|
||||
my %hash = split(' ', $$string);
|
||||
my @hash;
|
||||
if (defined($value)) {
|
||||
if (defined($multiple)) {
|
||||
$hash{$key} = $hash{$key} * $multiple + $value;
|
||||
} else {
|
||||
$hash{$key} = $value;
|
||||
}
|
||||
local $" = ' ';
|
||||
@hash = %hash;
|
||||
$$string = "@hash";
|
||||
} else {
|
||||
@hash = %hash;
|
||||
}
|
||||
return ($hash{$key}, scalar(@hash) / 2);
|
||||
}
|
Загрузка…
Ссылка в новой задаче