2019-04-25 08:04:09 +02:00
|
|
|
#!/usr/bin/env perl
|
|
|
|
|
|
|
|
# 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/.
|
|
|
|
|
2019-09-01 20:01:18 +02:00
|
|
|
package Plugins::Spinach::Rank;
|
2019-04-25 08:04:09 +02:00
|
|
|
|
|
|
|
use warnings;
|
|
|
|
use strict;
|
|
|
|
|
2019-07-11 03:40:53 +02:00
|
|
|
use feature 'unicode_strings';
|
|
|
|
|
2019-04-25 08:04:09 +02:00
|
|
|
use FindBin;
|
|
|
|
use lib "$FindBin::RealBin/../../..";
|
|
|
|
|
2019-09-01 20:01:18 +02:00
|
|
|
use Plugins::Spinach::Stats;
|
2019-04-27 23:40:28 +02:00
|
|
|
use Math::Expression::Evaluator;
|
2019-04-25 08:04:09 +02:00
|
|
|
|
|
|
|
sub new {
|
2020-02-15 23:38:32 +01:00
|
|
|
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;
|
2019-04-25 08:04:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
sub initialize {
|
2020-02-15 23:38:32 +01:00
|
|
|
my ($self, %conf) = @_;
|
|
|
|
$self->{pbot} = $conf{pbot} // Carp::croak("Missing pbot reference to " . __FILE__);
|
|
|
|
$self->{channel} = $conf{channel} // Carp::croak("Missing channel reference to " . __FILE__);
|
|
|
|
$self->{filename} = $conf{filename} // 'stats.sqlite';
|
|
|
|
$self->{stats} = Plugins::Spinach::Stats->new(pbot => $self->{pbot}, filename => $self->{filename});
|
2019-04-25 08:04:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
sub sort_generic {
|
2020-02-15 23:38:32 +01:00
|
|
|
my ($self, $key) = @_;
|
|
|
|
if ($self->{rank_direction} eq '+') { return $b->{$key} <=> $a->{$key}; }
|
|
|
|
else { return $a->{$key} <=> $b->{$key}; }
|
2019-04-25 08:04:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
sub print_generic {
|
2020-02-15 23:38:32 +01:00
|
|
|
my ($self, $key, $player) = @_;
|
|
|
|
return undef if $player->{games_played} == 0;
|
|
|
|
return "$player->{nick}: $player->{$key}";
|
2019-04-25 08:04:09 +02:00
|
|
|
}
|
|
|
|
|
2019-04-26 21:44:20 +02:00
|
|
|
sub print_avg_score {
|
2020-02-15 23:38:32 +01:00
|
|
|
my ($self, $player) = @_;
|
|
|
|
return undef if $player->{games_played} == 0;
|
|
|
|
my $result = int $player->{avg_score};
|
|
|
|
return "$player->{nick}: $result";
|
2019-04-26 21:44:20 +02:00
|
|
|
}
|
|
|
|
|
2019-04-25 08:04:09 +02:00
|
|
|
sub sort_bad_lies {
|
2020-02-15 23:38:32 +01:00
|
|
|
my ($self) = @_;
|
|
|
|
if ($self->{rank_direction} eq '+') { return $b->{questions_played} - $b->{good_lies} <=> $a->{questions_played} - $a->{good_lies}; }
|
|
|
|
else { return $a->{questions_played} - $a->{good_lies} <=> $b->{questions_played} - $b->{good_lies}; }
|
2019-04-25 08:04:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
sub print_bad_lies {
|
2020-02-15 23:38:32 +01:00
|
|
|
my ($self, $player) = @_;
|
|
|
|
return undef if $player->{games_played} == 0;
|
|
|
|
my $result = $player->{questions_played} - $player->{good_lies};
|
|
|
|
return "$player->{nick}: $result";
|
2019-04-25 08:04:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
sub sort_mentions {
|
2020-02-15 23:38:32 +01:00
|
|
|
my ($self) = @_;
|
|
|
|
if ($self->{rank_direction} eq '+') {
|
|
|
|
return $b->{games_played} - $b->{times_first} - $b->{times_second} - $b->{times_third} <=> $a->{games_played} - $a->{times_first} - $a->{times_second} -
|
|
|
|
$a->{times_third};
|
|
|
|
} else {
|
|
|
|
return $a->{games_played} - $a->{times_first} - $a->{times_second} - $a->{times_third} <=> $b->{games_played} - $b->{times_first} - $b->{times_second} -
|
|
|
|
$b->{times_third};
|
|
|
|
}
|
2019-04-25 08:04:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
sub print_mentions {
|
2020-02-15 23:38:32 +01:00
|
|
|
my ($self, $player) = @_;
|
|
|
|
return undef if $player->{games_played} == 0;
|
|
|
|
my $result = $player->{games_played} - $player->{times_first} - $player->{times_second} - $player->{times_third};
|
|
|
|
return "$player->{nick}: $result";
|
2019-04-25 08:04:09 +02:00
|
|
|
}
|
|
|
|
|
2019-04-27 23:40:28 +02:00
|
|
|
sub sort_expr {
|
2020-02-15 23:38:32 +01:00
|
|
|
my ($self) = @_;
|
|
|
|
|
|
|
|
my $result = eval {
|
|
|
|
my $result_a = $self->{expr}->val(
|
|
|
|
{
|
|
|
|
highscore => $a->{high_score},
|
|
|
|
lowscore => $a->{low_score},
|
|
|
|
avgscore => $a->{avg_score},
|
|
|
|
goodlies => $a->{good_lies},
|
|
|
|
badlies => $a->{questions_played} - $a->{good_lies},
|
|
|
|
first => $a->{times_first},
|
|
|
|
second => $a->{times_second},
|
|
|
|
third => $a->{times_third},
|
|
|
|
mentions => $a->{games_played} - $a->{times_first} - $a->{times_second} - $a->{times_third},
|
|
|
|
games => $a->{games_played},
|
|
|
|
questions => $a->{questions_played},
|
|
|
|
goodguesses => $a->{good_guesses},
|
|
|
|
badguesses => $a->{bad_guesses},
|
|
|
|
deceptions => $a->{players_deceived}
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
my $result_b = $self->{expr}->val(
|
|
|
|
{
|
|
|
|
highscore => $b->{high_score},
|
|
|
|
lowscore => $b->{low_score},
|
|
|
|
avgscore => $b->{avg_score},
|
|
|
|
goodlies => $b->{good_lies},
|
|
|
|
badlies => $b->{questions_played} - $b->{good_lies},
|
|
|
|
first => $b->{times_first},
|
|
|
|
second => $b->{times_second},
|
|
|
|
third => $b->{times_third},
|
|
|
|
mentions => $b->{games_played} - $b->{times_first} - $b->{times_second} - $b->{times_third},
|
|
|
|
games => $b->{games_played},
|
|
|
|
questions => $b->{questions_played},
|
|
|
|
goodguesses => $b->{good_guesses},
|
|
|
|
badguesses => $b->{bad_guesses},
|
|
|
|
deceptions => $b->{players_deceived}
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
if ($self->{rank_direction} eq '+') { return $result_b <=> $result_a; }
|
|
|
|
else { return $result_a <=> $result_b; }
|
|
|
|
};
|
2019-04-27 23:40:28 +02:00
|
|
|
|
2020-02-15 23:38:32 +01:00
|
|
|
if ($@) {
|
|
|
|
$self->{pbot}->{logger}->log("expr sort error: $@\n");
|
|
|
|
return 0;
|
2019-04-27 23:40:28 +02:00
|
|
|
}
|
|
|
|
|
2020-02-15 23:38:32 +01:00
|
|
|
return $result;
|
2019-04-27 23:40:28 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
sub print_expr {
|
2020-02-15 23:38:32 +01:00
|
|
|
my ($self, $player) = @_;
|
|
|
|
|
|
|
|
return undef if $player->{games_played} == 0;
|
|
|
|
|
|
|
|
my $result = eval {
|
|
|
|
$self->{expr}->val(
|
|
|
|
{
|
|
|
|
highscore => $player->{high_score},
|
|
|
|
lowscore => $player->{low_score},
|
|
|
|
avgscore => $player->{avg_score},
|
|
|
|
goodlies => $player->{good_lies},
|
|
|
|
badlies => $player->{questions_played} - $player->{good_lies},
|
|
|
|
first => $player->{times_first},
|
|
|
|
second => $player->{times_second},
|
|
|
|
third => $player->{times_third},
|
|
|
|
mentions => $player->{games_played} - $player->{times_first} - $player->{times_second} - $player->{times_third},
|
|
|
|
games => $player->{games_played},
|
|
|
|
questions => $player->{questions_played},
|
|
|
|
goodguesses => $player->{good_guesses},
|
|
|
|
badguesses => $player->{bad_guesses},
|
|
|
|
deceptions => $player->{players_deceived}
|
|
|
|
}
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
if ($@) {
|
|
|
|
$self->{pbot}->{logger}->log("Error in expr print: $@\n");
|
|
|
|
return undef;
|
|
|
|
}
|
|
|
|
|
|
|
|
return "$player->{nick}: $result";
|
2019-04-27 23:40:28 +02:00
|
|
|
}
|
|
|
|
|
2019-04-25 08:04:09 +02:00
|
|
|
sub rank {
|
2020-02-15 23:38:32 +01:00
|
|
|
my ($self, $arguments) = @_;
|
|
|
|
|
|
|
|
my %ranks = (
|
|
|
|
highscore => {
|
|
|
|
sort => sub { $self->sort_generic('high_score', @_) },
|
|
|
|
print => sub { $self->print_generic('high_score', @_) },
|
|
|
|
title => 'high score'
|
|
|
|
},
|
|
|
|
lowscore => {
|
|
|
|
sort => sub { $self->sort_generic('low_score', @_) },
|
|
|
|
print => sub { $self->print_generic('low_score', @_) },
|
|
|
|
title => 'low score'
|
|
|
|
},
|
|
|
|
avgscore => {
|
|
|
|
sort => sub { $self->sort_generic('avg_score', @_) },
|
|
|
|
print => sub { $self->print_avg_score(@_) },
|
|
|
|
title => 'average score'
|
|
|
|
},
|
|
|
|
goodlies => {
|
|
|
|
sort => sub { $self->sort_generic('good_lies', @_) },
|
|
|
|
print => sub { $self->print_generic('good_lies', @_) },
|
|
|
|
title => 'good lies'
|
|
|
|
},
|
|
|
|
badlies => {
|
|
|
|
sort => sub { $self->sort_bad_lies(@_) },
|
|
|
|
print => sub { $self->print_bad_lies(@_) },
|
|
|
|
title => 'bad lies'
|
|
|
|
},
|
|
|
|
first => {
|
|
|
|
sort => sub { $self->sort_generic('times_first', @_) },
|
|
|
|
print => sub { $self->print_generic('times_first', @_) },
|
|
|
|
title => 'first place'
|
|
|
|
},
|
|
|
|
second => {
|
|
|
|
sort => sub { $self->sort_generic('times_second', @_) },
|
|
|
|
print => sub { $self->print_generic('times_second', @_) },
|
|
|
|
title => 'second place'
|
|
|
|
},
|
|
|
|
third => {
|
|
|
|
sort => sub { $self->sort_generic('times_third', @_) },
|
|
|
|
print => sub { $self->print_generic('times_third', @_) },
|
|
|
|
title => 'third place'
|
|
|
|
},
|
|
|
|
mentions => {
|
|
|
|
sort => sub { $self->sort_mentions(@_) },
|
|
|
|
print => sub { $self->print_mentions(@_) },
|
|
|
|
title => 'mentions'
|
|
|
|
},
|
|
|
|
games => {
|
|
|
|
sort => sub { $self->sort_generic('games_played', @_) },
|
|
|
|
print => sub { $self->print_generic('games_played', @_) },
|
|
|
|
title => 'games played'
|
|
|
|
},
|
|
|
|
questions => {
|
|
|
|
sort => sub { $self->sort_generic('questions_played', @_) },
|
|
|
|
print => sub { $self->print_generic('questions_played', @_) },
|
|
|
|
title => 'questions played'
|
|
|
|
},
|
|
|
|
goodguesses => {
|
|
|
|
sort => sub { $self->sort_generic('good_guesses', @_) },
|
|
|
|
print => sub { $self->print_generic('good_guesses', @_) },
|
|
|
|
title => 'good guesses'
|
|
|
|
},
|
|
|
|
badguesses => {
|
|
|
|
sort => sub { $self->sort_generic('bad_guesses', @_) },
|
|
|
|
print => sub { $self->print_generic('bad_guesses', @_) },
|
|
|
|
title => 'bad guesses'
|
|
|
|
},
|
|
|
|
deceptions => {
|
|
|
|
sort => sub { $self->sort_generic('players_deceived', @_) },
|
|
|
|
print => sub { $self->print_generic('players_deceived', @_) },
|
|
|
|
title => 'players deceived'
|
|
|
|
},
|
|
|
|
expr => {
|
|
|
|
sort => sub { $self->sort_expr(@_) },
|
|
|
|
print => sub { $self->print_expr(@_) },
|
|
|
|
title => 'expr'
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
|
|
|
my @order = qw/highscore lowscore avgscore first second third mentions games questions goodlies badlies deceptions goodguesses badguesses expr/;
|
|
|
|
|
|
|
|
if (not $arguments) {
|
|
|
|
my $result = "Usage: rank [-]<keyword> [offset] or rank [-]<nick>; available keywords: ";
|
|
|
|
$result .= join ', ', @order;
|
|
|
|
$result .= ".\n";
|
|
|
|
$result .= "Prefix with a dash to invert sort.\n";
|
|
|
|
return $result;
|
|
|
|
}
|
2019-04-25 08:04:09 +02:00
|
|
|
|
2020-02-15 23:38:32 +01:00
|
|
|
$arguments = lc $arguments;
|
2019-04-25 08:04:09 +02:00
|
|
|
|
2020-02-15 23:38:32 +01:00
|
|
|
if ($arguments =~ s/^([+-])//) { $self->{rank_direction} = $1; }
|
|
|
|
else { $self->{rank_direction} = '+'; }
|
2019-04-25 08:04:09 +02:00
|
|
|
|
2020-02-15 23:38:32 +01:00
|
|
|
my $offset = 1;
|
|
|
|
if ($arguments =~ s/\s+(\d+)$//) { $offset = $1; }
|
2019-04-25 08:04:09 +02:00
|
|
|
|
2020-02-15 23:38:32 +01:00
|
|
|
my $opt_arg;
|
|
|
|
if ($arguments =~ /^expr/) {
|
|
|
|
if ($arguments =~ s/^expr (.+)$/expr/) { $opt_arg = $1; }
|
|
|
|
else { return "Usage: spinach rank expr <expression>"; }
|
2019-04-27 23:40:28 +02:00
|
|
|
}
|
2019-04-25 08:04:09 +02:00
|
|
|
|
2020-02-15 23:38:32 +01:00
|
|
|
if (not exists $ranks{$arguments}) {
|
|
|
|
$self->{stats}->begin;
|
|
|
|
my $player_id = $self->{stats}->get_player_id($arguments, $self->{channel}, 1);
|
|
|
|
my $player_data = $self->{stats}->get_player_data($player_id);
|
2019-04-25 08:04:09 +02:00
|
|
|
|
2020-02-15 23:38:32 +01:00
|
|
|
if (not defined $player_id) {
|
|
|
|
$self->{stats}->end;
|
|
|
|
return "I don't know anybody named $arguments.";
|
2019-04-25 08:04:09 +02:00
|
|
|
}
|
|
|
|
|
2020-02-15 23:38:32 +01:00
|
|
|
my $players = $self->{stats}->get_all_players($self->{channel});
|
|
|
|
my @rankings;
|
|
|
|
|
|
|
|
foreach my $key (@order) {
|
|
|
|
next if $key eq 'expr';
|
|
|
|
my $sort_method = $ranks{$key}->{sort};
|
|
|
|
@$players = sort $sort_method @$players;
|
|
|
|
|
|
|
|
my $rank = 0;
|
|
|
|
my $stats;
|
|
|
|
my $last_value = -1;
|
|
|
|
foreach my $player (@$players) {
|
|
|
|
$stats = $ranks{$key}->{print}->($player);
|
|
|
|
|
|
|
|
if (defined $stats) {
|
|
|
|
my ($value) = $stats =~ /[^:]+:\s+(.*)/;
|
|
|
|
$rank++ if $value ne $last_value;
|
|
|
|
$last_value = $value;
|
|
|
|
} else {
|
|
|
|
$rank++ if lc $player->{nick} eq $arguments;
|
|
|
|
}
|
|
|
|
|
|
|
|
last if lc $player->{nick} eq $arguments;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (not $rank) { push @rankings, "$ranks{key}->{title}: N/A"; }
|
|
|
|
else {
|
|
|
|
if (not $stats) { push @rankings, "$ranks{$key}->{title}: N/A"; }
|
|
|
|
else {
|
|
|
|
$stats =~ s/[^:]+:\s+//;
|
|
|
|
push @rankings, "$ranks{$key}->{title}: #$rank ($stats)";
|
|
|
|
}
|
|
|
|
}
|
2019-04-25 08:04:09 +02:00
|
|
|
}
|
|
|
|
|
2020-02-15 23:38:32 +01:00
|
|
|
my $result = "$player_data->{nick}'s rankings: ";
|
|
|
|
$result .= join ', ', @rankings;
|
|
|
|
$self->{stats}->end;
|
|
|
|
return $result;
|
|
|
|
}
|
2019-04-25 08:04:09 +02:00
|
|
|
|
2020-02-15 23:38:32 +01:00
|
|
|
$self->{stats}->begin;
|
|
|
|
my $players = $self->{stats}->get_all_players($self->{channel});
|
2019-04-25 08:04:09 +02:00
|
|
|
|
2020-02-15 23:38:32 +01:00
|
|
|
if ($arguments eq 'expr') {
|
|
|
|
$self->{expr} = eval { Math::Expression::Evaluator->new($opt_arg) };
|
|
|
|
if ($@) {
|
|
|
|
my $error = $@;
|
|
|
|
$error =~ s/ at .*//ms;
|
|
|
|
return "Bad expression: $error";
|
|
|
|
}
|
|
|
|
$self->{expr}->optimize;
|
2019-04-27 23:40:28 +02:00
|
|
|
}
|
2020-02-15 23:38:32 +01:00
|
|
|
|
|
|
|
my $sort_method = $ranks{$arguments}->{sort};
|
|
|
|
@$players = sort $sort_method @$players;
|
|
|
|
|
|
|
|
my @ranking;
|
|
|
|
my $rank = 0;
|
|
|
|
my $last_value = -1;
|
|
|
|
foreach my $player (@$players) {
|
|
|
|
my $entry = $ranks{$arguments}->{print}->($player);
|
|
|
|
if (defined $entry) {
|
|
|
|
my ($value) = $entry =~ /[^:]+:\s+(.*)/;
|
|
|
|
$rank++ if $value ne $last_value;
|
|
|
|
$last_value = $value;
|
|
|
|
next if $rank < $offset;
|
|
|
|
push @ranking, "#$rank $entry" if defined $entry;
|
|
|
|
last if scalar @ranking >= 15;
|
|
|
|
}
|
2019-04-25 08:04:09 +02:00
|
|
|
}
|
|
|
|
|
2020-02-15 23:38:32 +01:00
|
|
|
my $result;
|
2019-04-25 08:04:09 +02:00
|
|
|
|
2020-02-15 23:38:32 +01:00
|
|
|
if (not scalar @ranking) {
|
|
|
|
if ($offset > 1) { $result = "No rankings available for $self->{channel} at offset #$offset.\n"; }
|
|
|
|
else { $result = "No rankings available for $self->{channel} yet.\n"; }
|
2019-04-25 08:04:09 +02:00
|
|
|
} else {
|
2020-02-15 23:38:32 +01:00
|
|
|
if ($arguments eq 'expr') { $result = "Rankings for $opt_arg: "; }
|
|
|
|
else { $result = "Rankings for $ranks{$arguments}->{title}: "; }
|
|
|
|
$result .= join ', ', @ranking;
|
2019-04-25 08:04:09 +02:00
|
|
|
}
|
|
|
|
|
2020-02-15 23:38:32 +01:00
|
|
|
$self->{stats}->end;
|
|
|
|
return $result;
|
2019-04-25 08:04:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
1;
|