# This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. package PBot::Plugins::Spinach; use warnings; use strict; use feature 'switch'; no if $] >= 5.018, warnings => "experimental::smartmatch"; use Carp (); use DBI; use JSON; use Lingua::EN::Fractions qw/fraction2words/; use Lingua::EN::Numbers qw/num2en num2en_ordinal/; use Lingua::EN::Numbers::Years qw/year2en/; use Lingua::Stem qw/stem/; use Data::Dumper; sub new { Carp::croak("Options to " . __FILE__ . " should be key/value pairs, not hash reference") if ref $_[1] eq 'HASH'; my ($class, %conf) = @_; my $self = bless {}, $class; $self->initialize(%conf); return $self; } sub initialize { my ($self, %conf) = @_; $self->{pbot} = delete $conf{pbot} // Carp::croak("Missing pbot reference to " . __FILE__); $self->{pbot}->{commands}->register(sub { $self->spinach_cmd(@_) }, 'spinach', 0); $self->{pbot}->{timer}->register(sub { $self->spinach_timer }, 1, 'spinach timer'); $self->{pbot}->{event_dispatcher}->register_handler('irc.part', sub { $self->on_departure(@_) }); $self->{pbot}->{event_dispatcher}->register_handler('irc.quit', sub { $self->on_departure(@_) }); $self->{pbot}->{event_dispatcher}->register_handler('irc.kick', sub { $self->on_kick(@_) }); $self->{leaderboard_filename} = $self->{pbot}->{registry}->get_value('general', 'data_dir') . '/spinach/spinachlb.sqlite3'; $self->{questions_filename} = $self->{pbot}->{registry}->get_value('general', 'data_dir') . '/spinach/spinachq.json'; $self->create_database; $self->create_states; $self->load_questions; $self->{channel} = '##spinach'; $self->{choosecategory_max_count} = 2; $self->{picktruth_max_count} = 4; } sub unload { my $self = shift; $self->{pbot}->{commands}->unregister('spinach'); $self->{pbot}->{timer}->unregister('spinach timer'); } sub on_kick { my ($self, $event_type, $event) = @_; my ($nick, $user, $host) = ($event->{event}->nick, $event->{event}->user, $event->{event}->host); my ($victim, $reason) = ($event->{event}->to, $event->{event}->{args}[1]); my $channel = $event->{event}->{args}[0]; return 0 if lc $channel ne $self->{channel}; $self->player_left($nick, $user, $host); return 0; } sub on_departure { my ($self, $event_type, $event) = @_; my ($nick, $user, $host, $channel) = ($event->{event}->nick, $event->{event}->user, $event->{event}->host, $event->{event}->to); my $type = uc $event->{event}->type; return 0 if $type ne 'QUIT' and lc $channel ne $self->{channel}; $self->player_left($nick, $user, $host); return 0; } sub load_questions { my $self = shift; my $contents = do { open my $fh, '<', $self->{questions_filename} or do { $self->{pbot}->{logger}->log("Spinach: Failed to open $self->{questions_filename}: $!\n"); return; }; local $/; <$fh>; }; $self->{questions} = decode_json $contents; $self->{categories} = (); my $questions; foreach my $key (keys %{$self->{questions}}) { foreach my $question (@{$self->{questions}->{$key}}) { $self->{categories}{$question->{category}}++; $questions++; } } my $categories; foreach my $category (sort keys %{$self->{categories}}) { #$self->{pbot}->{logger}->log("Category [$category]: $self->{categories}{$category}\n"); $categories++; } $self->{pbot}->{logger}->log("Spinach: Loaded $questions questions in $categories categories.\n"); } sub create_database { my $self = shift; eval { $self->{dbh} = DBI->connect("dbi:SQLite:dbname=$self->{leaderboard_filename}", "", "", { RaiseError => 1, PrintError => 0, AutoInactiveDestroy => 1 }) or die $DBI::errstr; $self->{dbh}->do(<{dbh}->disconnect; }; $self->{pbot}->{logger}->log("Spinach create database failed: $@") if $@; } sub dbi_begin { my ($self) = @_; eval { $self->{dbh} = DBI->connect("dbi:SQLite:dbname=$self->{leaderboard_filename}", "", "", { RaiseError => 1, PrintError => 0, AutoInactiveDestroy => 1 }) or die $DBI::errstr; }; if ($@) { $self->{pbot}->{logger}->log("Error opening Spinach database: $@"); return 0; } else { return 1; } } sub dbi_end { my ($self) = @_; $self->{dbh}->disconnect; } my %color = ( white => "\x0300", black => "\x0301", blue => "\x0302", green => "\x0303", red => "\x0304", maroon => "\x0305", purple => "\x0306", orange => "\x0307", yellow => "\x0308", lightgreen => "\x0309", teal => "\x0310", cyan => "\x0311", lightblue => "\x0312", magneta => "\x0313", gray => "\x0314", lightgray => "\x0315", bold => "\x02", italics => "\x1D", underline => "\x1F", reverse => "\x16", reset => "\x0F", ); sub spinach_cmd { my ($self, $from, $nick, $user, $host, $arguments) = @_; $arguments = lc $arguments; $arguments =~ s/^\s+|\s+$//g; my $usage = "Usage: spinach start|stop|abort|join|exit|ready|players|kick|choose|lie|truth|score|show; for more information about a command: spinach help "; my $command; ($command, $arguments) = split / /, $arguments, 2; my ($channel, $result); given ($command) { when ('help') { given ($arguments) { when ('help') { return "Seriously?"; } when ('start') { return "Help is coming soon."; } when ('join') { return "Help is coming soon."; } when ('ready') { return "Help is coming soon."; } when ('exit') { return "Help is coming soon."; } when ('abort') { return "Help is coming soon."; } when ('stop') { return "Help is coming soon."; } when ('kick') { return "Help is coming soon."; } when ('players') { return "Help is coming soon."; } when ('score') { return "Help is coming soon."; } when ('leaderboard') { return "Help is coming soon."; } when ('choose') { return "Help is coming soon."; } when ('lie') { return "Help is coming soon."; } when ('truth') { return "Help is coming soon."; } when ('show') { return "Help is coming soon."; } default { if (length $arguments) { return "Spinach has no such command '$arguments'. I can't help you with that."; } else { return "Usage: spinach help "; } } } } when ('load') { my $admin = $self->{pbot}->{admins}->loggedin($self->{channel}, "$nick!$user\@$host"); if (not $admin or $admin->{level} < 90) { return "$nick: Sorry, only very powerful admins may reload the questions."; } $self->load_questions; } when ('start') { if ($self->{current_state} eq 'nogame') { $self->{current_state} = 'getplayers'; $self->{previous_state} = 'nogame'; $self->{state_data} = { players => [], counter => 0 }; return "/msg $self->{channel} Starting Spinach."; } else { return "Spinach is already started."; } } when ('leaderboard') { return "Coming soon."; } when ('score') { if (not @{$self->{state_data}->{players}}) { return "There is nobody playing right now."; } my $text = ''; my $comma = ''; foreach my $player (sort { $b->{score} <=> $a->{score} } @{$self->{state_data}->{players}}) { $text .= "$comma$player->{name}: $player->{score}"; $comma = '; '; } return $text; } when ('join') { if ($self->{current_state} eq 'nogame') { $self->{current_state} = 'getplayers'; } my $id = $self->{pbot}->{messagehistory}->{database}->get_message_account($nick, $user, $host); foreach my $player (@{$self->{state_data}->{players}}) { if ($player->{id} == $id) { return "$nick: You have already joined this game."; } } my $player = { id => $id, name => $nick, score => 0, ready => $self->{current_state} eq 'getplayers' ? 0 : 1, missedinputs => 0 }; push @{$self->{state_data}->{players}}, $player; $self->{state_data}->{counter} = 0; return "/msg $self->{channel} $nick has joined the game!"; } when ('ready') { if ($self->{current_state} eq 'nogame') { return "There is no game started. Use `start` to begin a new game."; } elsif ($self->{current_state} ne 'getplayers') { return "There is a game in progress. You may join after the game is over."; } my $id = $self->{pbot}->{messagehistory}->{database}->get_message_account($nick, $user, $host); foreach my $player (@{$self->{state_data}->{players}}) { if ($player->{id} == $id) { $player->{ready} = 1; $player->{score} = 0; return "/msg $self->{channel} $nick is ready!"; } } return "$nick: You haven't joined this game yet."; } when ('exit') { my $id = $self->{pbot}->{messagehistory}->{database}->get_message_account($nick, $user, $host); my $removed = 0; for (my $i = 0; $i < @{$self->{state_data}->{players}}; $i++) { if ($self->{state_data}->{players}->[$i]->{id} == $id) { splice @{$self->{state_data}->{players}}, $i--, 1; $removed = 1; } } if ($removed) { if ($self->{state_data}->{current_player} >= @{$self->{state_data}->{players}}) { $self->{state_data}->{current_player} = @{$self->{state_data}->{players}} - 1 } return "/msg $self->{channel} $nick has left the game!"; } else { return "$nick: But you are not even playing the game."; } } when ('abort') { if (not $self->{pbot}->{admins}->loggedin($self->{channel}, "$nick!$user\@$host")) { return "$nick: Sorry, only admins may abort the game."; } $self->{current_state} = 'gameover'; return "/msg $self->{channel} $nick: The game has been aborted."; } when ('players') { if ($self->{current_state} ne 'getplayers') { return "This command can only be used during the 'Waiting for players' stage. Try the `score` command."; } my @names; foreach my $player (@{$self->{state_data}->{players}}) { if (not $player->{ready}) { push @names, "$player->{name} $color{red}(not ready)$color{reset}"; } else { push @names, $player->{name}; } } my $players = join ', ', @names; $players = 'none' if not @names; return "Current players: $players"; } when ('stop') { if ($self->{current_state} ne 'getplayers') { return "This command can only be used during the 'Waiting for players' stage. To stop a game in progress, use the `abort` command."; } $self->{current_state} = 'nogame'; return "/msg $self->{channel} $nick: The game has been stopped."; } when ('kick') { if (not $self->{pbot}->{admins}->loggedin($self->{channel}, "$nick!$user\@$host")) { return "$nick: Sorry, only admins may kick people from the game."; } if (not length $arguments) { return "Usage: spinach kick "; } my $removed = 0; for (my $i = 0; $i < @{$self->{state_data}->{players}}; $i++) { if (lc $self->{state_data}->{players}->[$i]->{name} eq $arguments) { splice @{$self->{state_data}->{players}}, $i--, 1; $removed = 1; } } if ($removed) { if ($self->{state_data}->{current_player} >= @{$self->{state_data}->{players}}) { $self->{state_data}->{current_player} = @{$self->{state_data}->{players}} - 1 } return "/msg $self->{channel} $nick: $arguments has been kicked from the game."; } else { return "$nick: $arguments isn't even in the game."; } } when ('choose') { if ($self->{current_state} !~ /choosecategory$/) { return "$nick: It is not time to choose a category."; } if (not length $arguments) { return "Usage: spinach choose "; } my $id = $self->{pbot}->{messagehistory}->{database}->get_message_account($nick, $user, $host); if (not @{$self->{state_data}->{players}} or $id != $self->{state_data}->{players}->[$self->{state_data}->{current_player}]->{id}) { return "$nick: It is not your turn to choose a category."; } if ($arguments !~ /^[0-9]+$/) { return "$nick: Please choose a category number. $self->{state_data}->{categories_text}"; } $arguments--; if ($arguments < 0 or $arguments >= @{$self->{state_data}->{category_options}}) { return "$nick: Choice out of range. Please choose a valid category. $self->{state_data}->{categories_text}"; } $self->{state_data}->{current_category} = $self->{state_data}->{category_options}->[$arguments]; return "/msg $self->{channel} $nick has chosen $self->{state_data}->{current_category}!"; } when ('lie') { if ($self->{current_state} !~ /getlies$/) { return "$nick: It is not time to submit a lie!"; } if (not length $arguments) { return "Usage: spinach lie "; } my $id = $self->{pbot}->{messagehistory}->{database}->get_message_account($nick, $user, $host); my $player; foreach my $i (@{$self->{state_data}->{players}}) { if ($i->{id} == $id) { $player = $i; last; } } if (not $player) { return "$nick: You are not playing in this game. Please wait until the next game."; } $arguments = $self->normalize_text($arguments); $self->{pbot}->{logger}->log("Normalized lie: [$arguments]\n"); my $found_truth = 0; if (not $self->validate_lie($self->{state_data}->{current_question}->{answer}, $arguments)) { $found_truth = 1; } foreach my $alt (@{$self->{state_data}->{current_question}->{alternativeSpellings}}) { if (not $self->validate_lie($alt, $arguments)) { $found_truth = 1; last; } } if ($found_truth) { return "$nick: Your lie is too similar to the truth! Please submit a different lie."; } my $changed = exists $player->{lie}; $player->{lie} = $arguments; if ($changed) { return "/msg $self->{channel} $nick has changed their lie!"; } else { return "/msg $self->{channel} $nick has submitted a lie!"; } } when ('truth') { if ($self->{current_state} !~ /findtruth$/) { return "$nick: It is not time to find the truth!"; } if (not length $arguments) { return "Usage: spinach truth "; } my $id = $self->{pbot}->{messagehistory}->{database}->get_message_account($nick, $user, $host); my $player; foreach my $i (@{$self->{state_data}->{players}}) { if ($i->{id} == $id) { $player = $i; last; } } if (not $player) { return "$nick: You are not playing in this game. Please wait until the next game."; } if ($arguments !~ /^[0-9]+$/) { return "$nick: Please select a truth number. $self->{state_data}->{current_choices_text}"; } $arguments--; if ($arguments < 0 or $arguments >= @{$self->{state_data}->{current_choices}}) { return "$nick: Selection out of range. Please select a valid truth: $self->{state_data}->{current_choices_text}"; } my $changed = exists $player->{truth}; $player->{truth} = uc $self->{state_data}->{current_choices}->[$arguments]; if ($player->{truth} eq $player->{lie}) { delete $player->{truth}; return "$nick: You cannot select your own lie!"; } if ($changed) { return "/msg $self->{channel} $nick has selected a different truth!"; } else { return "/msg $self->{channel} $nick has selected a truth!"; } } when ('show') { if ($self->{current_state} =~ /(?:getlies|findtruth|showlies)$/) { $self->showquestion($self->{state_data}); return; } return "$nick: There is nothing to show right now."; } default { return $usage; } } return $result; } sub spinach_timer { my $self = shift; $self->run_one_state; } sub player_left { my ($self, $nick, $user, $host) = @_; my $id = $self->{pbot}->{messagehistory}->{database}->get_message_account($nick, $user, $host); my $removed = 0; for (my $i = 0; $i < @{$self->{state_data}->{players}}; $i++) { if ($self->{state_data}->{players}->[$i]->{id} == $id) { splice @{$self->{state_data}->{players}}, $i--, 1; $self->{pbot}->{conn}->privmsg($self->{channel}, "$color{bold}$nick has left the game!$color{reset}"); $removed = 1; } } if ($removed) { if ($self->{state_data}->{current_player} >= @{$self->{state_data}->{players}}) { $self->{state_data}->{current_player} = @{$self->{state_data}->{players}} - 1 } return "/msg $self->{channel} $nick has left the game!"; } } sub add_new_suggestions { my ($self, $state) = @_; my $question = undef; my $modified = 0; foreach my $player (@{$state->{players}}) { if ($player->{deceived}) { $self->{pbot}->{logger}->log("Adding new suggestion for $state->{current_question}->{id}: $player->{deceived}\n"); if (not grep { lc $_ eq lc $player->{deceived} } @{$state->{current_question}->{suggestions}}) { if (not defined $question) { foreach my $q (@{$self->{questions}->{questions}}) { if ($q->{id} == $state->{current_question}->{id}) { $question = $q; last; } } } push @{$question->{suggestions}}, uc $player->{deceived}; $modified = 1; } } } if ($modified) { my $json = encode_json $self->{questions}; open my $fh, '>', $self->{questions_filename} or do { $self->{pbot}->{logger}->log("Failed to open Spinach file: $!\n"); return; }; print $fh "$json\n"; close $fh; } } sub run_one_state { my $self = shift; if (not @{$self->{state_data}->{players}} and $self->{current_state} =~ /r\dq\d/) { $self->{pbot}->{conn}->privmsg($self->{channel}, "$color{bold}All players have left the game!$color{reset}"); $self->{current_state} = 'nogame'; } my $current_state = $self->{current_state}; my $state_data = $self->{state_data}; if (not defined $current_state) { $self->{pbot}->{logger}->log("Spinach state broke.\n"); return; } if ($self->{previous_state} ne $self->{current_state}) { $state_data->{newstate} = 1; $state_data->{ticks} = 1; $state_data->{first_tock} = 1; } else { $state_data->{newstate} = 0; } if ($state_data->{newstate}) { $self->{pbot}->{logger}->log("Spinach: New state: $self->{current_state}\n" . Dumper $state_data); } $state_data = $self->{states}{$current_state}{sub}($state_data); $state_data->{previous_result} = $state_data->{result}; $self->{previous_state} = $self->{current_state}; $self->{current_state} = $self->{states}{$current_state}{trans}{$state_data->{result}}; $self->{state_data} = $state_data; $self->{state_data}->{ticks}++; } sub create_states { my $self = shift; $self->{pbot}->{logger}->log("Spinach: Creating game state machine\n"); $self->{previous_state} = ''; $self->{current_state} = 'nogame'; $self->{state_data} = { players => [], ticks => 0, newstate => 1 }; $self->{states}{'nogame'}{sub} = sub { $self->nogame(@_) }; $self->{states}{'nogame'}{trans}{start} = 'getplayers'; $self->{states}{'nogame'}{trans}{nogame} = 'nogame'; $self->{states}{'getplayers'}{sub} = sub { $self->getplayers(@_) }; $self->{states}{'getplayers'}{trans}{stop} = 'nogame'; $self->{states}{'getplayers'}{trans}{wait} = 'getplayers'; $self->{states}{'getplayers'}{trans}{allready} = 'round1'; $self->{states}{'round1'}{sub} = sub { $self->round1(@_) }; $self->{states}{'round1'}{trans}{next} = 'round1q1'; $self->{states}{'round1q1'}{sub} = sub { $self->round1q1(@_) }; $self->{states}{'round1q1'}{trans}{next} = 'r1q1choosecategory'; $self->{states}{'r1q1choosecategory'}{sub} = sub { $self->r1q1choosecategory(@_) }; $self->{states}{'r1q1choosecategory'}{trans}{wait} = 'r1q1choosecategory'; $self->{states}{'r1q1choosecategory'}{trans}{next} = 'r1q1showquestion'; $self->{states}{'r1q1showquestion'}{sub} = sub { $self->r1q1showquestion(@_) }; $self->{states}{'r1q1showquestion'}{trans}{wait} = 'r1q1showquestion'; $self->{states}{'r1q1showquestion'}{trans}{next} = 'r1q1getlies'; $self->{states}{'r1q1getlies'}{sub} = sub { $self->r1q1getlies(@_) }; $self->{states}{'r1q1getlies'}{trans}{wait} = 'r1q1getlies'; $self->{states}{'r1q1getlies'}{trans}{next} = 'r1q1findtruth'; $self->{states}{'r1q1findtruth'}{sub} = sub { $self->r1q1findtruth(@_) }; $self->{states}{'r1q1findtruth'}{trans}{wait} = 'r1q1findtruth'; $self->{states}{'r1q1findtruth'}{trans}{next} = 'r1q1showlies'; $self->{states}{'r1q1showlies'}{sub} = sub { $self->r1q1showlies(@_) }; $self->{states}{'r1q1showlies'}{trans}{wait} = 'r1q1showlies'; $self->{states}{'r1q1showlies'}{trans}{next} = 'r1q1showtruth'; $self->{states}{'r1q1showtruth'}{sub} = sub { $self->r1q1showtruth(@_) }; $self->{states}{'r1q1showtruth'}{trans}{wait} = 'r1q1showtruth'; $self->{states}{'r1q1showtruth'}{trans}{next} = 'r1q1reveallies'; $self->{states}{'r1q1reveallies'}{sub} = sub { $self->r1q1reveallies(@_) }; $self->{states}{'r1q1reveallies'}{trans}{wait} = 'r1q1reveallies'; $self->{states}{'r1q1reveallies'}{trans}{next} = 'r1q1showscore'; $self->{states}{'r1q1showscore'}{sub} = sub { $self->r1q1showscore(@_) }; $self->{states}{'r1q1showscore'}{trans}{wait} = 'r1q1showscore'; $self->{states}{'r1q1showscore'}{trans}{next} = 'round1q2'; $self->{states}{'round1q2'}{sub} = sub { $self->round1q2(@_) }; $self->{states}{'round1q2'}{trans}{next} = 'r1q2choosecategory'; $self->{states}{'r1q2choosecategory'}{sub} = sub { $self->r1q2choosecategory(@_) }; $self->{states}{'r1q2choosecategory'}{trans}{wait} = 'r1q2choosecategory'; $self->{states}{'r1q2choosecategory'}{trans}{next} = 'r1q2showquestion'; $self->{states}{'r1q2showquestion'}{sub} = sub { $self->r1q2showquestion(@_) }; $self->{states}{'r1q2showquestion'}{trans}{wait} = 'r1q2showquestion'; $self->{states}{'r1q2showquestion'}{trans}{next} = 'r1q2getlies'; $self->{states}{'r1q2getlies'}{sub} = sub { $self->r1q2getlies(@_) }; $self->{states}{'r1q2getlies'}{trans}{wait} = 'r1q2getlies'; $self->{states}{'r1q2getlies'}{trans}{next} = 'r1q2findtruth'; $self->{states}{'r1q2findtruth'}{sub} = sub { $self->r1q2findtruth(@_) }; $self->{states}{'r1q2findtruth'}{trans}{wait} = 'r1q2findtruth'; $self->{states}{'r1q2findtruth'}{trans}{next} = 'r1q2showlies'; $self->{states}{'r1q2showlies'}{sub} = sub { $self->r1q2showlies(@_) }; $self->{states}{'r1q2showlies'}{trans}{wait} = 'r1q2showlies'; $self->{states}{'r1q2showlies'}{trans}{next} = 'r1q2showtruth'; $self->{states}{'r1q2showtruth'}{sub} = sub { $self->r1q2showtruth(@_) }; $self->{states}{'r1q2showtruth'}{trans}{wait} = 'r1q2showtruth'; $self->{states}{'r1q2showtruth'}{trans}{next} = 'r1q2reveallies'; $self->{states}{'r1q2reveallies'}{sub} = sub { $self->r1q2reveallies(@_) }; $self->{states}{'r1q2reveallies'}{trans}{wait} = 'r1q2reveallies'; $self->{states}{'r1q2reveallies'}{trans}{next} = 'r1q2showscore'; $self->{states}{'r1q2showscore'}{sub} = sub { $self->r1q2showscore(@_) }; $self->{states}{'r1q2showscore'}{trans}{wait} = 'r1q2showscore'; $self->{states}{'r1q2showscore'}{trans}{next} = 'round1q3'; $self->{states}{'round1q3'}{sub} = sub { $self->round1q3(@_) }; $self->{states}{'round1q3'}{trans}{next} = 'r1q3choosecategory'; $self->{states}{'r1q3choosecategory'}{sub} = sub { $self->r1q3choosecategory(@_) }; $self->{states}{'r1q3choosecategory'}{trans}{wait} = 'r1q3choosecategory'; $self->{states}{'r1q3choosecategory'}{trans}{next} = 'r1q3showquestion'; $self->{states}{'r1q3showquestion'}{sub} = sub { $self->r1q3showquestion(@_) }; $self->{states}{'r1q3showquestion'}{trans}{wait} = 'r1q3showquestion'; $self->{states}{'r1q3showquestion'}{trans}{next} = 'r1q3getlies'; $self->{states}{'r1q3getlies'}{sub} = sub { $self->r1q3getlies(@_) }; $self->{states}{'r1q3getlies'}{trans}{wait} = 'r1q3getlies'; $self->{states}{'r1q3getlies'}{trans}{next} = 'r1q3findtruth'; $self->{states}{'r1q3findtruth'}{sub} = sub { $self->r1q3findtruth(@_) }; $self->{states}{'r1q3findtruth'}{trans}{wait} = 'r1q3findtruth'; $self->{states}{'r1q3findtruth'}{trans}{next} = 'r1q3showlies'; $self->{states}{'r1q3showlies'}{sub} = sub { $self->r1q3showlies(@_) }; $self->{states}{'r1q3showlies'}{trans}{wait} = 'r1q3showlies'; $self->{states}{'r1q3showlies'}{trans}{next} = 'r1q3showtruth'; $self->{states}{'r1q3showtruth'}{sub} = sub { $self->r1q3showtruth(@_) }; $self->{states}{'r1q3showtruth'}{trans}{wait} = 'r1q3showtruth'; $self->{states}{'r1q3showtruth'}{trans}{next} = 'r1q3reveallies'; $self->{states}{'r1q3reveallies'}{sub} = sub { $self->r1q3reveallies(@_) }; $self->{states}{'r1q3reveallies'}{trans}{wait} = 'r1q3reveallies'; $self->{states}{'r1q3reveallies'}{trans}{next} = 'r1q3showscore'; $self->{states}{'r1q3showscore'}{sub} = sub { $self->r1q3showscore(@_) }; $self->{states}{'r1q3showscore'}{trans}{wait} = 'r1q3showscore'; $self->{states}{'r1q3showscore'}{trans}{next} = 'round2'; $self->{states}{'round2'}{sub} = sub { $self->round2(@_) }; $self->{states}{'round2'}{trans}{next} = 'round2q1'; $self->{states}{'round2q1'}{sub} = sub { $self->round2q1(@_) }; $self->{states}{'round2q1'}{trans}{next} = 'r2q1choosecategory'; $self->{states}{'r2q1choosecategory'}{sub} = sub { $self->r2q1choosecategory(@_) }; $self->{states}{'r2q1choosecategory'}{trans}{wait} = 'r2q1choosecategory'; $self->{states}{'r2q1choosecategory'}{trans}{next} = 'r2q1showquestion'; $self->{states}{'r2q1showquestion'}{sub} = sub { $self->r2q1showquestion(@_) }; $self->{states}{'r2q1showquestion'}{trans}{wait} = 'r2q1showquestion'; $self->{states}{'r2q1showquestion'}{trans}{next} = 'r2q1getlies'; $self->{states}{'r2q1getlies'}{sub} = sub { $self->r2q1getlies(@_) }; $self->{states}{'r2q1getlies'}{trans}{wait} = 'r2q1getlies'; $self->{states}{'r2q1getlies'}{trans}{next} = 'r2q1findtruth'; $self->{states}{'r2q1findtruth'}{sub} = sub { $self->r2q1findtruth(@_) }; $self->{states}{'r2q1findtruth'}{trans}{wait} = 'r2q1findtruth'; $self->{states}{'r2q1findtruth'}{trans}{next} = 'r2q1showlies'; $self->{states}{'r2q1showlies'}{sub} = sub { $self->r2q1showlies(@_) }; $self->{states}{'r2q1showlies'}{trans}{wait} = 'r2q1showlies'; $self->{states}{'r2q1showlies'}{trans}{next} = 'r2q1showtruth'; $self->{states}{'r2q1showtruth'}{sub} = sub { $self->r2q1showtruth(@_) }; $self->{states}{'r2q1showtruth'}{trans}{wait} = 'r2q1showtruth'; $self->{states}{'r2q1showtruth'}{trans}{next} = 'r2q1reveallies'; $self->{states}{'r2q1reveallies'}{sub} = sub { $self->r2q1reveallies(@_) }; $self->{states}{'r2q1reveallies'}{trans}{wait} = 'r2q1reveallies'; $self->{states}{'r2q1reveallies'}{trans}{next} = 'r2q1showscore'; $self->{states}{'r2q1showscore'}{sub} = sub { $self->r2q1showscore(@_) }; $self->{states}{'r2q1showscore'}{trans}{wait} = 'r2q1showscore'; $self->{states}{'r2q1showscore'}{trans}{next} = 'round2q2'; $self->{states}{'round2q2'}{sub} = sub { $self->round2q2(@_) }; $self->{states}{'round2q2'}{trans}{next} = 'r2q2choosecategory'; $self->{states}{'r2q2choosecategory'}{sub} = sub { $self->r2q2choosecategory(@_) }; $self->{states}{'r2q2choosecategory'}{trans}{wait} = 'r2q2choosecategory'; $self->{states}{'r2q2choosecategory'}{trans}{next} = 'r2q2showquestion'; $self->{states}{'r2q2showquestion'}{sub} = sub { $self->r2q2showquestion(@_) }; $self->{states}{'r2q2showquestion'}{trans}{wait} = 'r2q2showquestion'; $self->{states}{'r2q2showquestion'}{trans}{next} = 'r2q2getlies'; $self->{states}{'r2q2getlies'}{sub} = sub { $self->r2q2getlies(@_) }; $self->{states}{'r2q2getlies'}{trans}{wait} = 'r2q2getlies'; $self->{states}{'r2q2getlies'}{trans}{next} = 'r2q2findtruth'; $self->{states}{'r2q2findtruth'}{sub} = sub { $self->r2q2findtruth(@_) }; $self->{states}{'r2q2findtruth'}{trans}{wait} = 'r2q2findtruth'; $self->{states}{'r2q2findtruth'}{trans}{next} = 'r2q2showlies'; $self->{states}{'r2q2showlies'}{sub} = sub { $self->r2q2showlies(@_) }; $self->{states}{'r2q2showlies'}{trans}{wait} = 'r2q2showlies'; $self->{states}{'r2q2showlies'}{trans}{next} = 'r2q2showtruth'; $self->{states}{'r2q2showtruth'}{sub} = sub { $self->r2q2showtruth(@_) }; $self->{states}{'r2q2showtruth'}{trans}{wait} = 'r2q2showtruth'; $self->{states}{'r2q2showtruth'}{trans}{next} = 'r2q2reveallies'; $self->{states}{'r2q2reveallies'}{sub} = sub { $self->r2q2reveallies(@_) }; $self->{states}{'r2q2reveallies'}{trans}{wait} = 'r2q2reveallies'; $self->{states}{'r2q2reveallies'}{trans}{next} = 'r2q2showscore'; $self->{states}{'r2q2showscore'}{sub} = sub { $self->r2q2showscore(@_) }; $self->{states}{'r2q2showscore'}{trans}{wait} = 'r2q2showscore'; $self->{states}{'r2q2showscore'}{trans}{next} = 'round2q3'; $self->{states}{'round2q3'}{sub} = sub { $self->round2q3(@_) }; $self->{states}{'round2q3'}{trans}{next} = 'r2q3choosecategory'; $self->{states}{'r2q3choosecategory'}{sub} = sub { $self->r2q3choosecategory(@_) }; $self->{states}{'r2q3choosecategory'}{trans}{wait} = 'r2q3choosecategory'; $self->{states}{'r2q3choosecategory'}{trans}{next} = 'r2q3showquestion'; $self->{states}{'r2q3showquestion'}{sub} = sub { $self->r2q3showquestion(@_) }; $self->{states}{'r2q3showquestion'}{trans}{wait} = 'r2q3showquestion'; $self->{states}{'r2q3showquestion'}{trans}{next} = 'r2q3getlies'; $self->{states}{'r2q3getlies'}{sub} = sub { $self->r2q3getlies(@_) }; $self->{states}{'r2q3getlies'}{trans}{wait} = 'r2q3getlies'; $self->{states}{'r2q3getlies'}{trans}{next} = 'r2q3findtruth'; $self->{states}{'r2q3findtruth'}{sub} = sub { $self->r2q3findtruth(@_) }; $self->{states}{'r2q3findtruth'}{trans}{wait} = 'r2q3findtruth'; $self->{states}{'r2q3findtruth'}{trans}{next} = 'r2q3showlies'; $self->{states}{'r2q3showlies'}{sub} = sub { $self->r2q3showlies(@_) }; $self->{states}{'r2q3showlies'}{trans}{wait} = 'r2q3showlies'; $self->{states}{'r2q3showlies'}{trans}{next} = 'r2q3showtruth'; $self->{states}{'r2q3showtruth'}{sub} = sub { $self->r2q3showtruth(@_) }; $self->{states}{'r2q3showtruth'}{trans}{wait} = 'r2q3showtruth'; $self->{states}{'r2q3showtruth'}{trans}{next} = 'r2q3reveallies'; $self->{states}{'r2q3reveallies'}{sub} = sub { $self->r2q3reveallies(@_) }; $self->{states}{'r2q3reveallies'}{trans}{wait} = 'r2q3reveallies'; $self->{states}{'r2q3reveallies'}{trans}{next} = 'r2q3showscore'; $self->{states}{'r2q3showscore'}{sub} = sub { $self->r2q3showscore(@_) }; $self->{states}{'r2q3showscore'}{trans}{wait} = 'r2q3showscore'; $self->{states}{'r2q3showscore'}{trans}{next} = 'round3'; $self->{states}{'round3'}{sub} = sub { $self->round3(@_) }; $self->{states}{'round3'}{trans}{next} = 'round3q1'; $self->{states}{'round3q1'}{sub} = sub { $self->round3q1(@_) }; $self->{states}{'round3q1'}{trans}{next} = 'r3q1choosecategory'; $self->{states}{'r3q1choosecategory'}{sub} = sub { $self->r3q1choosecategory(@_) }; $self->{states}{'r3q1choosecategory'}{trans}{wait} = 'r3q1choosecategory'; $self->{states}{'r3q1choosecategory'}{trans}{next} = 'r3q1showquestion'; $self->{states}{'r3q1showquestion'}{sub} = sub { $self->r3q1showquestion(@_) }; $self->{states}{'r3q1showquestion'}{trans}{wait} = 'r3q1showquestion'; $self->{states}{'r3q1showquestion'}{trans}{next} = 'r3q1getlies'; $self->{states}{'r3q1getlies'}{sub} = sub { $self->r3q1getlies(@_) }; $self->{states}{'r3q1getlies'}{trans}{wait} = 'r3q1getlies'; $self->{states}{'r3q1getlies'}{trans}{next} = 'r3q1findtruth'; $self->{states}{'r3q1findtruth'}{sub} = sub { $self->r3q1findtruth(@_) }; $self->{states}{'r3q1findtruth'}{trans}{wait} = 'r3q1findtruth'; $self->{states}{'r3q1findtruth'}{trans}{next} = 'r3q1showlies'; $self->{states}{'r3q1showlies'}{sub} = sub { $self->r3q1showlies(@_) }; $self->{states}{'r3q1showlies'}{trans}{wait} = 'r3q1showlies'; $self->{states}{'r3q1showlies'}{trans}{next} = 'r3q1showtruth'; $self->{states}{'r3q1showtruth'}{sub} = sub { $self->r3q1showtruth(@_) }; $self->{states}{'r3q1showtruth'}{trans}{wait} = 'r3q1showtruth'; $self->{states}{'r3q1showtruth'}{trans}{next} = 'r3q1reveallies'; $self->{states}{'r3q1reveallies'}{sub} = sub { $self->r3q1reveallies(@_) }; $self->{states}{'r3q1reveallies'}{trans}{wait} = 'r3q1reveallies'; $self->{states}{'r3q1reveallies'}{trans}{next} = 'r3q1showscore'; $self->{states}{'r3q1showscore'}{sub} = sub { $self->r3q1showscore(@_) }; $self->{states}{'r3q1showscore'}{trans}{wait} = 'r3q1showscore'; $self->{states}{'r3q1showscore'}{trans}{next} = 'round3q2'; $self->{states}{'round3q2'}{sub} = sub { $self->round3q2(@_) }; $self->{states}{'round3q2'}{trans}{next} = 'r3q2choosecategory'; $self->{states}{'r3q2choosecategory'}{sub} = sub { $self->r3q2choosecategory(@_) }; $self->{states}{'r3q2choosecategory'}{trans}{wait} = 'r3q2choosecategory'; $self->{states}{'r3q2choosecategory'}{trans}{next} = 'r3q2showquestion'; $self->{states}{'r3q2showquestion'}{sub} = sub { $self->r3q2showquestion(@_) }; $self->{states}{'r3q2showquestion'}{trans}{wait} = 'r3q2showquestion'; $self->{states}{'r3q2showquestion'}{trans}{next} = 'r3q2getlies'; $self->{states}{'r3q2getlies'}{sub} = sub { $self->r3q2getlies(@_) }; $self->{states}{'r3q2getlies'}{trans}{wait} = 'r3q2getlies'; $self->{states}{'r3q2getlies'}{trans}{next} = 'r3q2findtruth'; $self->{states}{'r3q2findtruth'}{sub} = sub { $self->r3q2findtruth(@_) }; $self->{states}{'r3q2findtruth'}{trans}{wait} = 'r3q2findtruth'; $self->{states}{'r3q2findtruth'}{trans}{next} = 'r3q2showlies'; $self->{states}{'r3q2showlies'}{sub} = sub { $self->r3q2showlies(@_) }; $self->{states}{'r3q2showlies'}{trans}{wait} = 'r3q2showlies'; $self->{states}{'r3q2showlies'}{trans}{next} = 'r3q2showtruth'; $self->{states}{'r3q2showtruth'}{sub} = sub { $self->r3q2showtruth(@_) }; $self->{states}{'r3q2showtruth'}{trans}{wait} = 'r3q2showtruth'; $self->{states}{'r3q2showtruth'}{trans}{next} = 'r3q2reveallies'; $self->{states}{'r3q2reveallies'}{sub} = sub { $self->r3q2reveallies(@_) }; $self->{states}{'r3q2reveallies'}{trans}{wait} = 'r3q2reveallies'; $self->{states}{'r3q2reveallies'}{trans}{next} = 'r3q2showscore'; $self->{states}{'r3q2showscore'}{sub} = sub { $self->r3q2showscore(@_) }; $self->{states}{'r3q2showscore'}{trans}{wait} = 'r3q2showscore'; $self->{states}{'r3q2showscore'}{trans}{next} = 'round3q3'; $self->{states}{'round3q3'}{sub} = sub { $self->round3q3(@_) }; $self->{states}{'round3q3'}{trans}{next} = 'r3q3choosecategory'; $self->{states}{'r3q3choosecategory'}{sub} = sub { $self->r3q3choosecategory(@_) }; $self->{states}{'r3q3choosecategory'}{trans}{wait} = 'r3q3choosecategory'; $self->{states}{'r3q3choosecategory'}{trans}{next} = 'r3q3showquestion'; $self->{states}{'r3q3showquestion'}{sub} = sub { $self->r3q3showquestion(@_) }; $self->{states}{'r3q3showquestion'}{trans}{wait} = 'r3q3showquestion'; $self->{states}{'r3q3showquestion'}{trans}{next} = 'r3q3getlies'; $self->{states}{'r3q3getlies'}{sub} = sub { $self->r3q3getlies(@_) }; $self->{states}{'r3q3getlies'}{trans}{wait} = 'r3q3getlies'; $self->{states}{'r3q3getlies'}{trans}{next} = 'r3q3findtruth'; $self->{states}{'r3q3findtruth'}{sub} = sub { $self->r3q3findtruth(@_) }; $self->{states}{'r3q3findtruth'}{trans}{wait} = 'r3q3findtruth'; $self->{states}{'r3q3findtruth'}{trans}{next} = 'r3q3showlies'; $self->{states}{'r3q3showlies'}{sub} = sub { $self->r3q3showlies(@_) }; $self->{states}{'r3q3showlies'}{trans}{wait} = 'r3q3showlies'; $self->{states}{'r3q3showlies'}{trans}{next} = 'r3q3showtruth'; $self->{states}{'r3q3showtruth'}{sub} = sub { $self->r3q3showtruth(@_) }; $self->{states}{'r3q3showtruth'}{trans}{wait} = 'r3q3showtruth'; $self->{states}{'r3q3showtruth'}{trans}{next} = 'r3q3reveallies'; $self->{states}{'r3q3reveallies'}{sub} = sub { $self->r3q3reveallies(@_) }; $self->{states}{'r3q3reveallies'}{trans}{wait} = 'r3q3reveallies'; $self->{states}{'r3q3reveallies'}{trans}{next} = 'r3q3showscore'; $self->{states}{'r3q3showscore'}{sub} = sub { $self->r3q3showscore(@_) }; $self->{states}{'r3q3showscore'}{trans}{wait} = 'r3q3showscore'; $self->{states}{'r3q3showscore'}{trans}{next} = 'round4'; $self->{states}{'round4'}{sub} = sub { $self->round4(@_) }; $self->{states}{'round4'}{trans}{next} = 'round4q1'; $self->{states}{'round4q1'}{sub} = sub { $self->round4q1(@_) }; $self->{states}{'round4q1'}{trans}{next} = 'r4q1choosecategory'; $self->{states}{'r4q1choosecategory'}{sub} = sub { $self->r4q1choosecategory(@_) }; $self->{states}{'r4q1choosecategory'}{trans}{wait} = 'r4q1choosecategory'; $self->{states}{'r4q1choosecategory'}{trans}{next} = 'r4q1showquestion'; $self->{states}{'r4q1showquestion'}{sub} = sub { $self->r4q1showquestion(@_) }; $self->{states}{'r4q1showquestion'}{trans}{wait} = 'r4q1showquestion'; $self->{states}{'r4q1showquestion'}{trans}{next} = 'r4q1getlies'; $self->{states}{'r4q1getlies'}{sub} = sub { $self->r4q1getlies(@_) }; $self->{states}{'r4q1getlies'}{trans}{wait} = 'r4q1getlies'; $self->{states}{'r4q1getlies'}{trans}{next} = 'r4q1findtruth'; $self->{states}{'r4q1findtruth'}{sub} = sub { $self->r4q1findtruth(@_) }; $self->{states}{'r4q1findtruth'}{trans}{wait} = 'r4q1findtruth'; $self->{states}{'r4q1findtruth'}{trans}{next} = 'r4q1showlies'; $self->{states}{'r4q1showlies'}{sub} = sub { $self->r4q1showlies(@_) }; $self->{states}{'r4q1showlies'}{trans}{wait} = 'r4q1showlies'; $self->{states}{'r4q1showlies'}{trans}{next} = 'r4q1showtruth'; $self->{states}{'r4q1showtruth'}{sub} = sub { $self->r4q1showtruth(@_) }; $self->{states}{'r4q1showtruth'}{trans}{wait} = 'r4q1showtruth'; $self->{states}{'r4q1showtruth'}{trans}{next} = 'r4q1reveallies'; $self->{states}{'r4q1reveallies'}{sub} = sub { $self->r4q1reveallies(@_) }; $self->{states}{'r4q1reveallies'}{trans}{wait} = 'r4q1reveallies'; $self->{states}{'r4q1reveallies'}{trans}{next} = 'r4q1showscore'; $self->{states}{'r4q1showscore'}{sub} = sub { $self->r4q1showscore(@_) }; $self->{states}{'r4q1showscore'}{trans}{wait} = 'r4q1showscore'; $self->{states}{'r4q1showscore'}{trans}{next} = 'gameover'; $self->{states}{'gameover'}{sub} = sub { $self->gameover(@_) }; $self->{states}{'gameover'}{trans}{next} = 'getplayers'; } sub normalize_text { my ($self, $text) = @_; $text =~ s/^\s+|\s+$//g; $text =~ s/\s+/ /g; $text = substr($text, 0, 60); my @words = split /\b/, $text; my @result; foreach my $word (@words) { my $newword = $word; if ($word =~ m/^\d{4}$/ and $word >= 1700 and $word <= 2100) { $newword = year2en($word); } elsif ($word =~ m/^\d+$/) { $newword = num2en($word); } elsif ($word =~ m/^(\d+)(?:st|nd|rd|th)$/i) { $newword = num2en_ordinal($1); } elsif ($word =~ m/^(\d+\.\d+)(?:st|nd|rd|th)?$/i) { $newword = num2en($1); } elsif ($word =~ m{^(\d+\s*/\s*\d+)(?:st|nd|rd|th)?$}i) { $newword = fraction2words($1); } push @result, $newword; } $text = uc join '', @result; return substr $text, 0, 60; } sub validate_lie { my ($self, $truth, $lie) = @_; my %truth_words = @{stem map { $_ => 1 } grep { /^\w+$/ } split /\b/, $truth}; my $truth_word_count = keys %truth_words; my %lie_words = @{stem map { $_ => 1 } grep { /^\w+$/ } split /\b/, $lie}; my $lie_word_count = keys %lie_words; my $count = 0; foreach my $word (keys %truth_words) { if (exists $lie_words{$word}) { $count++; } } if ($count == $truth_word_count) { return 0; } $count = 0; foreach my $word (keys %lie_words) { if (exists $truth_words{$word}) { $count++; } } if ($count == $truth_word_count) { return 0; } my $stripped_truth = $truth; $stripped_truth =~ s/(?:\s|\p{PosixPunct})+//g; my $stripped_lie = $lie; $stripped_lie =~ s/(?:\s|\p{PosixPunct})+//g; if ($stripped_truth eq $stripped_lie) { return 0; } return 1; } # generic state subroutines sub choosecategory { my ($self, $state) = @_; if ($state->{init}) { delete $state->{current_category}; $state->{current_player}++; if ($state->{current_player} >= @{$state->{players}}) { $state->{current_player} = 0; } my @choices; my @categories = keys %{$self->{categories}}; my $no_infinite_loops = 0; while (1) { my $cat = $categories[rand @categories]; $self->{pbot}->{logger}->log("random cat: [$cat]\n"); if (not grep { $_ eq $cat } @choices) { push @choices, $cat; } last if @choices == 6; last if ++$no_infinite_loops > 200; } $state->{categories_text} = ''; my $i = 1; my $comma = ''; foreach my $choice (@choices) { $state->{categories_text} .= "$comma$i) $choice"; $i++; $comma = "; "; } $state->{category_options} = \@choices; delete $state->{init}; } if (exists $state->{current_category} or not @{$state->{players}}) { return 'next'; } my $tock; if ($state->{first_tock}) { $tock = 3; } else { $tock = 15; } if ($state->{ticks} % $tock == 0) { delete $state->{first_tock}; if (exists $state->{random_category}) { delete $state->{random_category}; my $category = $state->{category_options}->[rand @{$state->{category_options}}]; $self->{pbot}->{conn}->privmsg($self->{channel}, "$color{bold}Category: $category!$color{reset}"); $state->{current_category} = $category; return 'next'; } if (++$state->{counter} > $state->{max_count}) { $state->{players}->[$state->{current_player}]->{missedinputs}++ ; my $name = $state->{players}->[$state->{current_player}]->{name}; my $category = $state->{category_options}->[rand @{$state->{category_options}}]; $self->{pbot}->{conn}->privmsg($self->{channel}, "$color{bold}$name took too long to choose. Randomly choosing: $category!$color{reset}"); $state->{current_category} = $category; return 'next'; } my $name = $state->{players}->[$state->{current_player}]->{name}; my $red = $state->{counter} == $state->{max_count} ? $color{red} : ''; $self->{pbot}->{conn}->privmsg($self->{channel}, "$name: $red$color{bold}$state->{counter}/$state->{max_count} Choose a category via `/msg candide choose`:"); $self->{pbot}->{conn}->privmsg($self->{channel}, "$color{bold}$state->{categories_text}$color{reset}"); return 'wait'; } if (exists $state->{current_category}) { return 'next'; } else { return 'wait'; } } sub getnewquestion { my ($self, $state) = @_; if ($state->{ticks} % 3 == 0) { my @questions = grep { $_->{category} eq $state->{current_category} } @{$self->{questions}->{questions}}; $state->{current_question} = $questions[rand @questions]; $state->{current_question}->{answer} = $self->normalize_text($state->{current_question}->{answer}); my @alts = map { $self->normalize_text($_) } @{$state->{current_question}->{alternativeSpellings}}; $state->{current_question}->{alternativeSpellings} = \@alts; foreach my $player (@{$state->{players}}) { delete $player->{lie}; delete $player->{truth}; delete $player->{deceived}; } return 'next'; } else { return 'wait'; } } sub showquestion { my ($self, $state) = @_; if (exists $state->{current_question}) { $self->{pbot}->{conn}->privmsg($self->{channel}, "$color{bold}$color{green}Current question:$color{reset}$color{bold} $state->{current_question}->{question}$color{reset}"); } else { $self->{pbot}->{conn}->privmsg($self->{channel}, "$color{bold}There is no current question.$color{reset}"); } } sub getlies { my ($self, $state) = @_; my $tock; if ($state->{first_tock}) { $tock = 3; } else { $tock = 15; } my @nolies; foreach my $player (@{$state->{players}}) { if (not exists $player->{lie}) { push @nolies, $player->{name}; } } return 'next' if not @nolies; if ($state->{ticks} % $tock == 0) { delete $state->{first_tock}; if (++$state->{counter} > $state->{max_count}) { my @missedinputs; foreach my $player (@{$state->{players}}) { if (not exists $player->{lie}) { push @missedinputs, $player->{name}; $player->{missedinputs}++; } } if (@missedinputs) { my $missed = join ', ', @missedinputs; $self->{pbot}->{conn}->privmsg($self->{channel}, "$color{bold}$missed failed to submit a lie in time!$color{reset}"); } return 'next'; } my $players = join ', ', @nolies; my $red = $state->{counter} == $state->{max_count} ? $color{red} : ''; $self->{pbot}->{conn}->privmsg($self->{channel}, "$players: $red$color{bold}$state->{counter}/$state->{max_count} Submit your lie now via `/msg candide lie`!$color{reset}"); } return 'wait'; } sub findtruth { my ($self, $state) = @_; my $tock; if ($state->{first_tock}) { $tock = 3; } else { $tock = 15; } my @notruth; foreach my $player (@{$state->{players}}) { if (not exists $player->{truth}) { push @notruth, $player->{name}; } } return 'next' if not @notruth; if ($state->{ticks} % $tock == 0) { delete $state->{first_tock}; if ($state->{init}) { delete $state->{init}; my @choices; my @suggestions = @{$state->{current_question}->{suggestions}}; my @lies; foreach my $player (@{$state->{players}}) { if ($player->{lie}) { if (not grep { $_ eq $player->{lie} } @lies) { push @lies, uc $player->{lie}; } } } while (1) { my $limit = @{$state->{players}} < 5 ? 5 : @{$state->{players}}; last if @choices >= $limit; if (@lies) { my $random = rand @lies; push @choices, $lies[$random]; splice @lies, $random, 1; next; } if (@suggestions) { my $random = rand @suggestions; my $suggestion = uc $suggestions[$random]; push @choices, $suggestion if not grep { $_ eq $suggestion } @choices; splice @suggestions, $random, 1; next; } last; } splice @choices, rand @choices, 0, uc $state->{current_question}->{answer}; $state->{correct_answer} = uc $state->{current_question}->{answer}; my $i = 0; my $comma = ''; my $text = ''; foreach my $choice (@choices) { ++$i; $text .= "$comma$i) $choice"; $comma = '; '; } $state->{current_choices_text} = $text; $state->{current_choices} = \@choices; } if (++$state->{counter} > $state->{max_count}) { my @missedinputs; foreach my $player (@{$state->{players}}) { if (not exists $player->{truth}) { push @missedinputs, $player->{name}; $player->{missedinputs}++; $player->{score} -= $state->{lie_points}; } } if (@missedinputs) { my $missed = join ', ', @missedinputs; $self->{pbot}->{conn}->privmsg($self->{channel}, "$color{bold}$missed failed to find the truth in time! They lose $state->{lie_points} points!$color{reset}"); } return 'next'; } my $players = join ', ', @notruth; my $red = $state->{counter} == $state->{max_count} ? $color{red} : ''; $self->{pbot}->{conn}->privmsg($self->{channel}, "$players: $red$color{bold}$state->{counter}/$state->{max_count} Find the truth now via `/msg candide truth`!"); $self->{pbot}->{conn}->privmsg($self->{channel}, "$color{bold}$state->{current_choices_text}$color{reset}"); } return 'wait'; } sub showlies { my ($self, $state) = @_; my @liars; my $player; my $tock; if ($state->{first_tock}) { $tock = 3; } else { $tock = 6; } if ($state->{ticks} % $tock == 0) { delete $state->{first_tock}; while ($state->{current_lie_player} < @{$state->{players}}) { $player = $state->{players}->[$state->{current_lie_player}]; $state->{current_lie_player}++; next if not exists $player->{truth}; foreach my $liar (@{$state->{players}}) { next if $liar->{id} == $player->{id}; next if not exists $liar->{lie}; if ($liar->{lie} eq $player->{truth}) { push @liars, $liar; } } last if @liars; if ($player->{truth} ne $state->{correct_answer}) { my $points = $state->{lie_points} * 0.25; $player->{score} -= $points; $self->{pbot}->{conn}->privmsg($self->{channel}, "$color{bold}$player->{name} fell for my lie: \"$player->{truth}\". -$points points!$color{reset}"); $player->{deceived} = $player->{truth}; if ($state->{current_lie_player} < @{$state->{players}}) { return 'wait'; } else { return 'next'; } } } if (@liars) { my $liars_text = ''; my $liars_no_apostrophe = ''; my $lie = $player->{truth}; my $gains = @liars == 1 ? 'gains' : 'gain'; my $comma = ''; foreach my $liar (@liars) { $liars_text .= "$comma$liar->{name}'s"; $liars_no_apostrophe .= "$comma$liar->{name}"; $comma = ', '; $liar->{score} += $state->{lie_points}; } $self->{pbot}->{conn}->privmsg($self->{channel}, "$color{bold}$player->{name} fell for $liars_text lie: \"$lie\". $liars_no_apostrophe $gains +$state->{lie_points} points!$color{reset}"); $player->{deceived} = $lie; } return 'next' if $state->{current_lie_player} >= @{$state->{players}}; return 'wait'; } return 'wait'; } sub showtruth { my ($self, $state) = @_; if ($state->{ticks} % 6 == 0) { my $players; my $comma = ''; my $count = 0; foreach my $player (@{$state->{players}}) { next if exists $player->{deceived}; if (exists $player->{truth} and $player->{truth} eq $state->{correct_answer}) { $count++; $players .= "$comma$player->{name}"; $comma = ', '; $player->{score} += $state->{truth_points}; } } if ($count) { $self->{pbot}->{conn}->privmsg($self->{channel}, "$color{bold}$players got the correct answer: \"$state->{correct_answer}\". +$state->{truth_points} points!$color{reset}"); } else { $self->{pbot}->{conn}->privmsg($self->{channel}, "$color{bold}Nobody found the truth! The answer was: $state->{correct_answer}$color{reset}"); } $self->add_new_suggestions($state); return 'next'; } else { return 'wait'; } } sub reveallies { my ($self, $state) = @_; if ($state->{ticks} % 3 == 0) { my $text = 'Revealing lies! '; my $comma = ''; foreach my $player (@{$state->{players}}) { next if not exists $player->{lie}; $text .= "$comma$player->{name}: $player->{lie}"; $comma = '; '; } $self->{pbot}->{conn}->privmsg($self->{channel}, "$color{bold}$text$color{reset}"); return 'next'; } else { return 'wait'; } } sub showscore { my ($self, $state) = @_; if ($state->{ticks} % 3 == 0) { my $text = ''; my $comma = ''; foreach my $player (sort { $b->{score} <=> $a->{score} } @{$state->{players}}) { $text .= "$comma$player->{name}: $player->{score}"; $comma = '; '; } $text = "none" if not length $text; $self->{pbot}->{conn}->privmsg($self->{channel}, "$color{bold}$color{green}Scores: $color{reset}$color{bold}$text$color{reset}"); return 'next'; } else { return 'wait'; } } # state subroutines sub nogame { my ($self, $state) = @_; $state->{result} = 'nogame'; return $state; } sub getplayers { my ($self, $state) = @_; my $players = $state->{players}; my @names; my $unready = @$players ? @$players : 1; foreach my $player (@$players) { if (not $player->{ready}) { push @names, "$player->{name} $color{red}(not ready)$color{reset}$color{bold}"; } else { $unready--; push @names, $player->{name}; } } if (not $unready) { $self->{pbot}->{conn}->privmsg($self->{channel}, "$color{bold}All players ready!$color{reset}"); $state->{result} = 'allready'; return $state; } my $tock; if ($state->{first_tock}) { $tock = 2; } else { $tock = 90; } if ($state->{ticks} % $tock == 0) { delete $state->{first_tock}; if (++$state->{counter} > 4) { $self->{pbot}->{conn}->privmsg($self->{channel}, "$color{bold}Not all players were ready in time. The game has been stopped.$color{reset}"); $state->{result} = 'stop'; return $state; } $players = join ', ', @names; $players = 'none' if not @names; my $msg = "Waiting for more players or for all players to ready up. Current players: $players"; $self->{pbot}->{conn}->privmsg($self->{channel}, "$color{bold}$msg$color{reset}"); } $state->{result} = 'wait'; return $state; } sub round1 { my ($self, $state) = @_; $state->{truth_points} = 500; $state->{lie_points} = 1000; $state->{my_lie_points} = $state->{lie_points} * 0.25; $state->{result} = 'next'; return $state; } sub round1q1 { my ($self, $state) = @_; $state->{init} = 1; $state->{counter} = 0; $state->{max_count} = $self->{choosecategory_max_count}; $state->{result} = 'next'; $self->{pbot}->{conn}->privmsg($self->{channel}, "$color{bold}Round 1/3, question 1/3! $state->{lie_points} for each lie. $state->{truth_points} for the truth.$color{reset}"); return $state; } sub r1q1choosecategory { my ($self, $state) = @_; $state->{result} = $self->choosecategory($state); return $state; } sub r1q1showquestion { my ($self, $state) = @_; my $result = $self->getnewquestion($state); if ($result eq 'next') { $self->showquestion($state); $state->{max_count} = $self->{picktruth_max_count}; $state->{counter} = 0; $state->{init} = 1; $state->{current_lie_player} = 0; $state->{result} = 'next'; } else { $state->{result} = 'wait'; } return $state; } sub r1q1getlies { my ($self, $state) = @_; $state->{result} = $self->getlies($state); if ($state->{result} eq 'next') { $state->{counter} = 0; $state->{init} = 1; } return $state; } sub r1q1findtruth { my ($self, $state) = @_; $state->{result} = $self->findtruth($state); return $state; } sub r1q1showlies { my ($self, $state) = @_; $state->{result} = $self->showlies($state); return $state; } sub r1q1showtruth { my ($self, $state) = @_; $state->{result} = $self->showtruth($state); return $state; } sub r1q1reveallies { my ($self, $state) = @_; $state->{result} = $self->reveallies($state); return $state; } sub r1q1showscore { my ($self, $state) = @_; $state->{result} = $self->showscore($state); return $state; } sub round1q2 { my ($self, $state) = @_; $state->{init} = 1; $state->{counter} = 0; $state->{max_count} = $self->{choosecategory_max_count}; $state->{result} = 'next'; $self->{pbot}->{conn}->privmsg($self->{channel}, "$color{bold}Round 1/3, question 2/3! $state->{lie_points} for each lie. $state->{truth_points} for the truth.$color{reset}"); return $state; } sub r1q2choosecategory { my ($self, $state) = @_; $state->{result} = $self->choosecategory($state); return $state; } sub r1q2showquestion { my ($self, $state) = @_; my $result = $self->getnewquestion($state); if ($result eq 'next') { $self->showquestion($state); $state->{max_count} = $self->{picktruth_max_count}; $state->{counter} = 0; $state->{init} = 1; $state->{current_lie_player} = 0; $state->{result} = 'next'; } else { $state->{result} = 'wait'; } return $state; } sub r1q2getlies { my ($self, $state) = @_; $state->{result} = $self->getlies($state); if ($state->{result} eq 'next') { $state->{counter} = 0; $state->{init} = 1; } return $state; } sub r1q2findtruth { my ($self, $state) = @_; $state->{result} = $self->findtruth($state); return $state; } sub r1q2showlies { my ($self, $state) = @_; $state->{result} = $self->showlies($state); return $state; } sub r1q2showtruth { my ($self, $state) = @_; $state->{result} = $self->showtruth($state); return $state; } sub r1q2reveallies { my ($self, $state) = @_; $state->{result} = $self->reveallies($state); return $state; } sub r1q2showscore { my ($self, $state) = @_; $state->{result} = $self->showscore($state); return $state; } sub round1q3 { my ($self, $state) = @_; $state->{init} = 1; $state->{max_count} = $self->{choosecategory_max_count}; $state->{counter} = 0; $state->{result} = 'next'; $self->{pbot}->{conn}->privmsg($self->{channel}, "$color{bold}Round 1/3, question 3/3! $state->{lie_points} for each lie. $state->{truth_points} for the truth.$color{reset}"); return $state; } sub r1q3choosecategory { my ($self, $state) = @_; $state->{result} = $self->choosecategory($state); return $state; } sub r1q3showquestion { my ($self, $state) = @_; my $result = $self->getnewquestion($state); if ($result eq 'next') { $self->showquestion($state); $state->{max_count} = $self->{picktruth_max_count}; $state->{counter} = 0; $state->{init} = 1; $state->{current_lie_player} = 0; $state->{result} = 'next'; } else { $state->{result} = 'wait'; } return $state; } sub r1q3getlies { my ($self, $state) = @_; $state->{result} = $self->getlies($state); if ($state->{result} eq 'next') { $state->{counter} = 0; $state->{init} = 1; } return $state; } sub r1q3findtruth { my ($self, $state) = @_; $state->{result} = $self->findtruth($state); return $state; } sub r1q3showlies { my ($self, $state) = @_; $state->{result} = $self->showlies($state); return $state; } sub r1q3showtruth { my ($self, $state) = @_; $state->{result} = $self->showtruth($state); return $state; } sub r1q3reveallies { my ($self, $state) = @_; $state->{result} = $self->reveallies($state); return $state; } sub r1q3showscore { my ($self, $state) = @_; $state->{result} = $self->showscore($state); return $state; } sub round2 { my ($self, $state) = @_; $state->{truth_points} = 750; $state->{lie_points} = 1500; $state->{my_lie_points} = $state->{lie_points} * 0.25; $state->{result} = 'next'; return $state; } sub round2q1 { my ($self, $state) = @_; $state->{init} = 1; $state->{max_count} = $self->{choosecategory_max_count}; $state->{counter} = 0; $state->{result} = 'next'; $self->{pbot}->{conn}->privmsg($self->{channel}, "$color{bold}Round 2/3, question 1/3! $state->{lie_points} for each lie. $state->{truth_points} for the truth.$color{reset}"); return $state; } sub r2q1choosecategory { my ($self, $state) = @_; $state->{result} = $self->choosecategory($state); return $state; } sub r2q1showquestion { my ($self, $state) = @_; my $result = $self->getnewquestion($state); if ($result eq 'next') { $self->showquestion($state); $state->{max_count} = $self->{picktruth_max_count}; $state->{counter} = 0; $state->{init} = 1; $state->{current_lie_player} = 0; $state->{result} = 'next'; } else { $state->{result} = 'wait'; } return $state; } sub r2q1getlies { my ($self, $state) = @_; $state->{result} = $self->getlies($state); if ($state->{result} eq 'next') { $state->{counter} = 0; $state->{init} = 1; } return $state; } sub r2q1findtruth { my ($self, $state) = @_; $state->{result} = $self->findtruth($state); return $state; } sub r2q1showlies { my ($self, $state) = @_; $state->{result} = $self->showlies($state); return $state; } sub r2q1showtruth { my ($self, $state) = @_; $state->{result} = $self->showtruth($state); return $state; } sub r2q1reveallies { my ($self, $state) = @_; $state->{result} = $self->reveallies($state); return $state; } sub r2q1showscore { my ($self, $state) = @_; $state->{result} = $self->showscore($state); return $state; } sub round2q2 { my ($self, $state) = @_; $state->{init} = 1; $state->{max_count} = $self->{choosecategory_max_count}; $state->{counter} = 0; $state->{result} = 'next'; $self->{pbot}->{conn}->privmsg($self->{channel}, "$color{bold}Round 2/3, question 2/3! $state->{lie_points} for each lie. $state->{truth_points} for the truth.$color{reset}"); return $state; } sub r2q2choosecategory { my ($self, $state) = @_; $state->{result} = $self->choosecategory($state); return $state; } sub r2q2showquestion { my ($self, $state) = @_; my $result = $self->getnewquestion($state); if ($result eq 'next') { $self->showquestion($state); $state->{max_count} = $self->{picktruth_max_count}; $state->{counter} = 0; $state->{init} = 1; $state->{current_lie_player} = 0; $state->{result} = 'next'; } else { $state->{result} = 'wait'; } return $state; } sub r2q2getlies { my ($self, $state) = @_; $state->{result} = $self->getlies($state); if ($state->{result} eq 'next') { $state->{counter} = 0; $state->{init} = 1; } return $state; } sub r2q2findtruth { my ($self, $state) = @_; $state->{result} = $self->findtruth($state); return $state; } sub r2q2showlies { my ($self, $state) = @_; $state->{result} = $self->showlies($state); return $state; } sub r2q2showtruth { my ($self, $state) = @_; $state->{result} = $self->showtruth($state); return $state; } sub r2q2reveallies { my ($self, $state) = @_; $state->{result} = $self->reveallies($state); return $state; } sub r2q2showscore { my ($self, $state) = @_; $state->{result} = $self->showscore($state); return $state; } sub round2q3 { my ($self, $state) = @_; $state->{init} = 1; $state->{max_count} = $self->{choosecategory_max_count}; $state->{counter} = 0; $state->{result} = 'next'; $self->{pbot}->{conn}->privmsg($self->{channel}, "$color{bold}Round 2/3, question 3/3! $state->{lie_points} for each lie. $state->{truth_points} for the truth.$color{reset}"); return $state; } sub r2q3choosecategory { my ($self, $state) = @_; $state->{result} = $self->choosecategory($state); return $state; } sub r2q3showquestion { my ($self, $state) = @_; my $result = $self->getnewquestion($state); if ($result eq 'next') { $self->showquestion($state); $state->{max_count} = $self->{picktruth_max_count}; $state->{counter} = 0; $state->{init} = 1; $state->{current_lie_player} = 0; $state->{result} = 'next'; } else { $state->{result} = 'wait'; } return $state; } sub r2q3getlies { my ($self, $state) = @_; $state->{result} = $self->getlies($state); if ($state->{result} eq 'next') { $state->{counter} = 0; $state->{init} = 1; } return $state; } sub r2q3findtruth { my ($self, $state) = @_; $state->{result} = $self->findtruth($state); return $state; } sub r2q3showlies { my ($self, $state) = @_; $state->{result} = $self->showlies($state); return $state; } sub r2q3showtruth { my ($self, $state) = @_; $state->{result} = $self->showtruth($state); return $state; } sub r2q3reveallies { my ($self, $state) = @_; $state->{result} = $self->reveallies($state); return $state; } sub r2q3showscore { my ($self, $state) = @_; $state->{result} = $self->showscore($state); return $state; } sub round3 { my ($self, $state) = @_; $state->{truth_points} = 1000; $state->{lie_points} = 2000; $state->{my_lie_points} = $state->{lie_points} * 0.25; $state->{result} = 'next'; return $state; } sub round3q1 { my ($self, $state) = @_; $state->{init} = 1; $state->{max_count} = $self->{choosecategory_max_count}; $state->{counter} = 0; $state->{result} = 'next'; $self->{pbot}->{conn}->privmsg($self->{channel}, "$color{bold}Round 3/3, question 1/3! $state->{lie_points} for each lie. $state->{truth_points} for the truth.$color{reset}"); return $state; } sub r3q1choosecategory { my ($self, $state) = @_; $state->{result} = $self->choosecategory($state); return $state; } sub r3q1showquestion { my ($self, $state) = @_; my $result = $self->getnewquestion($state); if ($result eq 'next') { $self->showquestion($state); $state->{max_count} = $self->{picktruth_max_count}; $state->{counter} = 0; $state->{init} = 1; $state->{current_lie_player} = 0; $state->{result} = 'next'; } else { $state->{result} = 'wait'; } return $state; } sub r3q1getlies { my ($self, $state) = @_; $state->{result} = $self->getlies($state); if ($state->{result} eq 'next') { $state->{counter} = 0; $state->{init} = 1; } return $state; } sub r3q1findtruth { my ($self, $state) = @_; $state->{result} = $self->findtruth($state); return $state; } sub r3q1showlies { my ($self, $state) = @_; $state->{result} = $self->showlies($state); return $state; } sub r3q1showtruth { my ($self, $state) = @_; $state->{result} = $self->showtruth($state); return $state; } sub r3q1reveallies { my ($self, $state) = @_; $state->{result} = $self->reveallies($state); return $state; } sub r3q1showscore { my ($self, $state) = @_; $state->{result} = $self->showscore($state); return $state; } sub round3q2 { my ($self, $state) = @_; $state->{init} = 1; $state->{max_count} = $self->{choosecategory_max_count}; $state->{counter} = 0; $state->{result} = 'next'; $self->{pbot}->{conn}->privmsg($self->{channel}, "$color{bold}Round 3/3, question 2/3! $state->{lie_points} for each lie. $state->{truth_points} for the truth.$color{reset}"); return $state; } sub r3q2choosecategory { my ($self, $state) = @_; $state->{result} = $self->choosecategory($state); return $state; } sub r3q2showquestion { my ($self, $state) = @_; my $result = $self->getnewquestion($state); if ($result eq 'next') { $self->showquestion($state); $state->{max_count} = $self->{picktruth_max_count}; $state->{counter} = 0; $state->{init} = 1; $state->{current_lie_player} = 0; $state->{result} = 'next'; } else { $state->{result} = 'wait'; } return $state; } sub r3q2getlies { my ($self, $state) = @_; $state->{result} = $self->getlies($state); if ($state->{result} eq 'next') { $state->{counter} = 0; $state->{init} = 1; } return $state; } sub r3q2findtruth { my ($self, $state) = @_; $state->{result} = $self->findtruth($state); return $state; } sub r3q2showlies { my ($self, $state) = @_; $state->{result} = $self->showlies($state); return $state; } sub r3q2showtruth { my ($self, $state) = @_; $state->{result} = $self->showtruth($state); return $state; } sub r3q2reveallies { my ($self, $state) = @_; $state->{result} = $self->reveallies($state); return $state; } sub r3q2showscore { my ($self, $state) = @_; $state->{result} = $self->showscore($state); return $state; } sub round3q3 { my ($self, $state) = @_; $state->{init} = 1; $state->{max_count} = $self->{choosecategory_max_count}; $state->{counter} = 0; $state->{result} = 'next'; $self->{pbot}->{conn}->privmsg($self->{channel}, "$color{bold}Round 3/3, question 3/3! $state->{lie_points} for each lie. $state->{truth_points} for the truth.$color{reset}"); return $state; } sub r3q3choosecategory { my ($self, $state) = @_; $state->{result} = $self->choosecategory($state); return $state; } sub r3q3showquestion { my ($self, $state) = @_; my $result = $self->getnewquestion($state); if ($result eq 'next') { $self->showquestion($state); $state->{max_count} = $self->{picktruth_max_count}; $state->{counter} = 0; $state->{init} = 1; $state->{current_lie_player} = 0; $state->{result} = 'next'; } else { $state->{result} = 'wait'; } return $state; } sub r3q3getlies { my ($self, $state) = @_; $state->{result} = $self->getlies($state); if ($state->{result} eq 'next') { $state->{counter} = 0; $state->{init} = 1; } return $state; } sub r3q3findtruth { my ($self, $state) = @_; $state->{result} = $self->findtruth($state); return $state; } sub r3q3showlies { my ($self, $state) = @_; $state->{result} = $self->showlies($state); return $state; } sub r3q3showtruth { my ($self, $state) = @_; $state->{result} = $self->showtruth($state); return $state; } sub r3q3reveallies { my ($self, $state) = @_; $state->{result} = $self->reveallies($state); return $state; } sub r3q3showscore { my ($self, $state) = @_; $state->{result} = $self->showscore($state); return $state; } sub round4 { my ($self, $state) = @_; $state->{truth_points} = 2000; $state->{lie_points} = 4000; $state->{my_lie_points} = $state->{lie_points} * 0.25; $state->{result} = 'next'; return $state; } sub round4q1 { my ($self, $state) = @_; $state->{init} = 1; $state->{random_category} = 1; $state->{max_count} = $self->{choosecategory_max_count}; $state->{counter} = 0; $state->{result} = 'next'; $self->{pbot}->{conn}->privmsg($self->{channel}, "$color{bold}FINAL ROUND! FINAL QUESTION! $state->{lie_points} for each lie. $state->{truth_points} for the truth.$color{reset}"); return $state; } sub r4q1choosecategory { my ($self, $state) = @_; $state->{result} = $self->choosecategory($state); return $state; } sub r4q1showquestion { my ($self, $state) = @_; my $result = $self->getnewquestion($state); if ($result eq 'next') { $self->showquestion($state); $state->{max_count} = $self->{picktruth_max_count}; $state->{counter} = 0; $state->{init} = 1; $state->{current_lie_player} = 0; $state->{result} = 'next'; } else { $state->{result} = 'wait'; } return $state; } sub r4q1getlies { my ($self, $state) = @_; $state->{result} = $self->getlies($state); if ($state->{result} eq 'next') { $state->{counter} = 0; $state->{init} = 1; } return $state; } sub r4q1findtruth { my ($self, $state) = @_; $state->{result} = $self->findtruth($state); return $state; } sub r4q1showlies { my ($self, $state) = @_; $state->{result} = $self->showlies($state); return $state; } sub r4q1showtruth { my ($self, $state) = @_; $state->{result} = $self->showtruth($state); return $state; } sub r4q1reveallies { my ($self, $state) = @_; $state->{result} = $self->reveallies($state); return $state; } sub r4q1showscore { my ($self, $state) = @_; $state->{result} = $self->showscore($state); return $state; } sub gameover { my ($self, $state) = @_; $self->{pbot}->{conn}->privmsg($self->{channel}, "$color{bold}Game over!$color{reset}"); my $players = $state->{players}; foreach my $player (@$players) { $player->{ready} = 0; $player->{missedinputs} = 0; } $state->{counter} = 0; $state->{result} = 'next'; return $state; } 1;