2014-07-28 06:29:05 +02:00
#!/usr/bin/env perl
2021-07-11 00:00:22 +02:00
# SPDX-FileCopyrightText: 2021 Pragmatic Software <pragma78@gmail.com>
# SPDX-License-Identifier: MIT
2017-03-05 22:33:31 +01:00
2014-07-28 06:29:05 +02:00
use warnings ;
use strict ;
use Text::Levenshtein qw( fastdistance ) ;
2014-08-04 08:25:56 +02:00
use Time::HiRes qw( gettimeofday ) ;
2017-06-08 04:31:07 +02:00
use Time::Duration qw( duration concise ) ;
2015-01-30 06:55:46 +01:00
use Fcntl qw( :flock ) ;
2014-07-28 06:29:05 +02:00
2018-03-12 16:52:43 +01:00
use lib "." ;
2015-05-23 11:27:53 +02:00
use QStatskeeper ;
2015-01-28 09:40:40 +01:00
use Scorekeeper ;
use IRCColors ;
my $ CJEOPARDY_DATA = 'data/cjeopardy.dat' ;
my $ CJEOPARDY_HINT = 'data/cjeopardy.hint' ;
my $ CJEOPARDY_LAST_ANSWER = 'data/cjeopardy.last_ans' ;
2014-07-28 06:29:05 +02:00
2014-08-04 00:20:54 +02:00
my $ hint_only_mode = 0 ;
2017-06-08 04:31:07 +02:00
my $ concise_duration = 1 ;
2014-08-04 00:20:54 +02:00
2014-08-04 08:25:56 +02:00
my $ nick = shift @ ARGV ;
2015-01-28 09:40:40 +01:00
my $ channel = shift @ ARGV ;
2014-07-28 06:29:05 +02:00
my $ text = join ( ' ' , @ ARGV ) ;
sub encode { my $ str = shift ; $ str =~ s/\\(.)/{sprintf "\\%03d", ord($1)}/ge ; return $ str ; }
sub decode { my $ str = shift ; $ str =~ s/\\(\d{3})/{"\\" . chr($1)}/ge ; return $ str }
if ( $ channel !~ /^#/ ) {
2014-08-31 22:21:09 +02:00
print "Sorry, C Jeopardy must be played in a channel. Feel free to join #cjeopardy.\n" ;
2014-07-28 06:29:05 +02:00
exit ;
}
2019-05-28 18:19:42 +02:00
while ( $ text =~ s/^\s*(is|are|the|a|an)\s+//i ) { } ;
2014-07-29 19:30:12 +02:00
$ text =~ s/\s*\?*$// ;
$ text =~ s/^\s+// ;
$ text =~ s/\s+$// ;
2014-08-04 00:20:54 +02:00
my $ lctext = lc $ text ;
2014-07-29 19:30:12 +02:00
2014-08-04 00:20:54 +02:00
if ( not length $ lctext ) {
2014-07-29 19:30:12 +02:00
print "What?\n" ;
exit ;
}
2014-07-28 06:29:05 +02:00
my @ data ;
2014-08-04 08:25:56 +02:00
2015-01-30 06:55:46 +01:00
open my $ semaphore , ">" , "$CJEOPARDY_DATA-$channel.lock" or die "Couldn't create semaphore lock: $!" ;
flock $ semaphore , LOCK_EX ;
2014-08-04 08:25:56 +02:00
my $ ret = open my $ fh , "<" , "$CJEOPARDY_LAST_ANSWER-$channel" ;
if ( defined $ ret ) {
my $ last_nick = <$fh> ;
my $ last_answers = <$fh> ;
my $ last_timestamp = <$fh> ;
close $ fh ;
chomp $ last_nick ;
2019-05-28 18:19:42 +02:00
if ( scalar gettimeofday - $ last_timestamp <= 15 ) {
2014-08-04 08:25:56 +02:00
$ ret = open $ fh , "<" , "$CJEOPARDY_DATA-$channel" ;
if ( defined $ ret ) {
@ data = <$fh> ;
close $ fh ;
}
my @ current_answers = map { decode $ _ } split /\|/ , encode $ data [ 1 ] if @ data ;
my @ valid_answers = map { decode $ _ } split /\|/ , encode $ last_answers ;
foreach my $ answer ( @ valid_answers ) {
chomp $ answer ;
$ answer =~ s/\\\|/|/g ;
$ answer =~ s/\s*{.*}\s*// ;
my $ skip_last ;
if ( @ current_answers ) {
foreach my $ current_answer ( @ current_answers ) {
chomp $ current_answer ;
$ current_answer =~ s/\\\|/|/g ;
$ current_answer =~ s/\s*{.*}\s*// ;
my $ distance = fastdistance ( lc $ answer , lc $ current_answer ) ;
my $ length = ( length ( $ answer ) > length ( $ current_answer ) ) ? length $ answer : length $ current_answer ;
if ( $ distance / $ length < 0.15 ) {
$ skip_last = 1 ;
last ;
}
}
}
last if $ skip_last ;
my $ distance = fastdistance ( $ lctext , lc $ answer ) ;
my $ length = ( length ( $ lctext ) > length ( $ answer ) ) ? length $ lctext : length $ answer ;
if ( $ distance / $ length < 0.15 ) {
if ( $ last_nick eq $ nick ) {
2015-01-28 09:40:40 +01:00
print "$color{red}Er, you already correctly answered that question.$color{reset}\n" ;
2014-08-04 08:25:56 +02:00
} else {
2015-02-01 01:41:05 +01:00
my $ elapsed = scalar gettimeofday - $ last_timestamp ;
my $ duration ;
if ( $ elapsed < 2 ) {
2015-05-21 11:25:08 +02:00
$ elapsed = 0.01 if $ elapsed <= 0.01 ;
2015-02-01 01:41:05 +01:00
$ duration = sprintf ( "%.2f" , $ elapsed ) ;
} else {
$ duration = sprintf ( "%d" , $ elapsed ) ;
}
print "$color{red}Too slow by $color{orange}$duration $color{red}second" . ( $ duration != 1 ? "s" : "" ) . "! $color{orange}$last_nick$color{red} got the correct answer.$color{reset}\n" ;
2014-08-04 08:25:56 +02:00
}
exit ;
}
}
}
}
if ( not @ data ) {
open $ fh , "<" , "$CJEOPARDY_DATA-$channel" or print "There is no open C Jeopardy question. Use `cjeopardy` to get a question.\n" and exit ;
@ data = <$fh> ;
close $ fh ;
}
2014-07-28 06:29:05 +02:00
2015-01-28 09:40:40 +01:00
my $ scores = Scorekeeper - > new ;
$ scores - > begin ;
my $ player_id = $ scores - > get_player_id ( $ nick , $ channel ) ;
my $ player_data = $ scores - > get_player_data ( $ player_id ) ;
2015-05-23 11:27:53 +02:00
my ( $ id ) = $ data [ 0 ] =~ m/^(\d+)/ ;
2014-07-29 21:00:06 +02:00
my @ valid_answers = map { decode $ _ } split /\|/ , encode $ data [ 1 ] ;
2014-07-28 06:29:05 +02:00
2015-05-23 11:27:53 +02:00
my $ qstats = QStatskeeper - > new ;
$ qstats - > begin ;
my $ qdata = $ qstats - > get_question_data ( $ id ) ;
$ qdata - > { last_touched } = gettimeofday ;
2014-08-31 22:21:09 +02:00
my $ incorrect_percentage = 100 ;
2014-07-28 06:29:05 +02:00
foreach my $ answer ( @ valid_answers ) {
chomp $ answer ;
$ answer =~ s/\\\|/|/g ;
2014-08-04 00:20:54 +02:00
my $ supplemental_text ;
if ( $ answer =~ s/\s*{(.*)}\s*$// ) {
$ supplemental_text = $ 1 ;
}
2015-02-01 01:41:05 +01:00
if ( $ answer =~ /^[+-]*[0-9]+$/ and $ lctext =~ /^[+-]*[0-9]+$/ ) {
2015-01-28 09:40:40 +01:00
my $ is_wrong = 0 ;
2015-01-24 17:05:47 +01:00
if ( $ lctext > $ answer ) {
2015-02-07 17:47:42 +01:00
print "$color{red}$lctext is too high!$color{reset}" ;
2015-01-28 09:40:40 +01:00
$ is_wrong = 1 ;
2015-01-24 17:05:47 +01:00
} elsif ( $ lctext < $ answer ) {
2015-02-07 17:47:42 +01:00
print "$color{red}$lctext is too low!$color{reset}" ;
2015-01-28 09:40:40 +01:00
$ is_wrong = 1 ;
2015-01-24 17:05:47 +01:00
}
2015-01-28 09:40:40 +01:00
goto WRONG_ANSWER if $ is_wrong ;
2015-01-24 17:05:47 +01:00
}
2014-08-04 00:20:54 +02:00
my $ distance = fastdistance ( $ lctext , lc $ answer ) ;
my $ length = ( length ( $ lctext ) > length ( $ answer ) ) ? length $ lctext : length $ answer ;
2014-07-28 06:29:05 +02:00
2014-08-31 22:21:09 +02:00
my $ percentage = $ distance / $ length * 100 ;
if ( $ percentage < $ incorrect_percentage ) {
2019-06-26 18:34:19 +02:00
$ incorrect_percentage = $ percentage ;
2014-08-31 22:21:09 +02:00
}
if ( $ percentage < 15 ) {
2014-07-28 06:29:05 +02:00
if ( $ distance == 0 ) {
2015-01-28 09:40:40 +01:00
print "'$color{green}$answer$color{reset}' is correct!" ;
2014-08-04 00:20:54 +02:00
} else {
2015-01-28 09:40:40 +01:00
print "'$color{green}$text$color{reset}' is close enough to '$color{green}$answer$color{reset}'. You are correct!"
2014-08-04 00:20:54 +02:00
}
if ( defined $ supplemental_text ) {
2015-02-01 01:41:05 +01:00
print " $color{purple}$supplemental_text$color{reset}" ;
}
my $ elapsed = scalar gettimeofday - $ data [ 2 ] ;
2017-06-08 04:31:07 +02:00
if ( $ concise_duration ) {
if ( $ elapsed < 60 ) {
printf " (%ds)\n" , $ elapsed ;
} else {
my $ duration = concise duration ( $ elapsed ) ;
print " ($duration)\n" ;
}
2014-07-28 06:29:05 +02:00
} else {
2017-06-08 04:31:07 +02:00
if ( $ elapsed < 60 ) {
printf " It took %.2f seconds to answer that question!\n" , $ elapsed ;
} else {
my $ duration = duration ( $ elapsed ) ;
print " It took $duration to answer that question.\n" ;
}
2014-07-28 06:29:05 +02:00
}
2015-05-23 11:27:53 +02:00
$ qdata - > { correct } + + ;
$ qdata - > { last_correct_time } = gettimeofday ;
$ qdata - > { last_correct_nick } = $ nick ;
2015-05-25 20:25:55 +02:00
if ( gettimeofday - $ qdata - > { last_asked } < 60 * 15 ) {
2015-05-23 11:58:58 +02:00
$ qdata - > { average_answer_time } *= $ qdata - > { correct } - 1 ;
2015-05-23 11:27:53 +02:00
$ qdata - > { average_answer_time } += $ elapsed ;
$ qdata - > { average_answer_time } /= $ qdata - > { correct } ;
}
if ( $ qdata - > { quickest_answer_time } == 0 or $ elapsed < $ qdata - > { quickest_answer_time } ) {
$ qdata - > { quickest_answer_time } = $ elapsed ;
2015-05-23 15:24:22 +02:00
$ qdata - > { quickest_answer_date } = gettimeofday ;
2015-05-23 11:27:53 +02:00
$ qdata - > { quickest_answer_nick } = $ nick ;
}
if ( $ elapsed > $ qdata - > { longest_answer_time } ) {
$ qdata - > { longest_answer_time } = $ elapsed ;
2015-05-23 15:24:22 +02:00
$ qdata - > { longest_answer_date } = gettimeofday ;
2015-05-23 11:27:53 +02:00
$ qdata - > { longest_answer_nick } = $ nick ;
}
2015-01-28 12:04:28 +01:00
my $ streakers = $ scores - > get_all_correct_streaks ( $ channel ) ;
2014-07-28 06:29:05 +02:00
2015-01-28 12:04:28 +01:00
foreach my $ streaker ( @$ streakers ) {
2016-12-07 02:57:50 +01:00
next if lc $ streaker - > { nick } eq lc $ nick ;
2014-08-04 08:25:56 +02:00
2015-01-28 12:04:28 +01:00
if ( $ streaker - > { correct_streak } >= 3 ) {
print "$color{orange}$nick$color{red} ended $color{orange}$streaker->{nick}$color{red}'s $color{orange}$streaker->{correct_streak}$color{red} correct answer streak!$color{reset}\n" ;
2014-08-04 00:20:54 +02:00
}
2015-01-28 12:04:28 +01:00
$ streaker - > { correct_streak } = 0 ;
$ scores - > update_player_data ( $ streaker - > { id } , $ streaker ) ;
2014-07-28 06:29:05 +02:00
}
2015-01-28 12:04:28 +01:00
2015-01-28 09:40:40 +01:00
$ player_data - > { correct_answers } + + ;
$ player_data - > { lifetime_correct_answers } + + ;
$ player_data - > { correct_streak } + + ;
$ player_data - > { last_correct_timestamp } = scalar gettimeofday ;
$ player_data - > { wrong_streak } = 0 ;
2015-02-01 01:41:05 +01:00
if ( $ player_data - > { quickest_correct } == 0 or $ elapsed < $ player_data - > { quickest_correct } ) {
$ player_data - > { quickest_correct } = $ elapsed ;
}
2015-01-28 09:40:40 +01:00
if ( $ player_data - > { correct_streak } > $ player_data - > { highest_correct_streak } ) {
$ player_data - > { highest_correct_streak } = $ player_data - > { correct_streak } ;
}
if ( $ player_data - > { highest_correct_streak } > $ player_data - > { lifetime_highest_correct_streak } ) {
$ player_data - > { lifetime_highest_correct_streak } = $ player_data - > { highest_correct_streak } ;
}
2015-05-21 11:25:08 +02:00
if ( $ player_data - > { correct_streak } == 1 ) {
$ player_data - > { correct_streak_timestamp } = scalar gettimeofday ;
}
my $ dont_print_streak = 0 ;
2015-05-22 13:03:39 +02:00
my $ t1 = $ player_data - > { lifetime_quickest_correct_streak } ? $ player_data - > { lifetime_quickest_correct_streak } : 32767 ;
my $ t2 = gettimeofday - $ player_data - > { correct_streak_timestamp } ;
my $ a1 = $ player_data - > { lifetime_highest_quick_correct_streak } ? $ player_data - > { lifetime_highest_quick_correct_streak } : 1 ;
my $ a2 = $ player_data - > { correct_streak } ? $ player_data - > { correct_streak } : 1 ;
my $ ratio1 = ( $ t1 + $ t1 ) / $ a1 ;
my $ ratio2 = ( $ t2 + $ t1 ) / $ a2 ;
2023-01-21 21:13:01 +01:00
#print STDERR "nick: $nick, t1 = $t1, t2 = $t2, a1 = $a1, a2 = $a2, ratio1 = $ratio1, ratio2 = $ratio2\n";
2015-05-22 13:03:39 +02:00
if ( $ ratio2 < $ ratio1 and $ player_data - > { correct_streak } >= 3 ) {
2015-05-21 11:25:08 +02:00
$ player_data - > { highest_quick_correct_streak } = $ player_data - > { correct_streak } ;
$ player_data - > { quickest_correct_streak } = gettimeofday - $ player_data - > { correct_streak_timestamp } ;
2015-05-22 13:03:39 +02:00
$ player_data - > { lifetime_highest_quick_correct_streak } = $ player_data - > { highest_quick_correct_streak } ;
$ player_data - > { lifetime_quickest_correct_streak } = $ player_data - > { quickest_correct_streak } ;
2015-05-21 11:25:08 +02:00
2015-05-22 13:03:39 +02:00
print "$color{orange}$nick$color{cyan} just set a new personal quickest correct answer streak of $color{orange}$player_data->{highest_quick_correct_streak} $color{cyan}correct answers in $color{orange}" , duration ( $ player_data - > { quickest_correct_streak } ) , "$color{cyan}!$color{reset}\n" ;
$ dont_print_streak = 1 ;
2015-05-21 11:25:08 +02:00
}
unless ( $ dont_print_streak ) {
my % streaks = (
3 = > "$color{orange}$nick$color{cyan} is on a $color{orange}3$color{cyan} correct answer streak!" ,
4 = > "$color{orange}$nick$color{cyan} is hot with a $color{orange}4$color{cyan} correct answer streak!" ,
5 = > "$color{orange}$nick$color{cyan} is on fire with a $color{orange}5$color{cyan} correct answer streak!" ,
6 = > "$color{orange}$nick$color{cyan} is ON FIRE with a $color{orange}6$color{cyan} correct answer streak!" ,
7 = > "$color{orange}$nick$color{cyan} is DOMINATING with a $color{orange}7$color{cyan} correct answer streak!" ,
8 = > "$color{orange}$nick$color{cyan} is DOMINATING with an $color{orange}8$color{cyan} correct answer streak!" ,
9 = > "$color{orange}$nick$color{cyan} is DOMINATING with a $color{orange}9$color{cyan} correct answer streak!" ,
10 = > "$color{orange}$nick$color{cyan} IS UNTOUCHABLE WITH A $color{orange}10$color{cyan} CORRECT ANSWER STREAK!"
) ;
if ( exists $ streaks { $ player_data - > { correct_streak } } ) {
print "$streaks{$player_data->{correct_streak}}$color{reset}\n" ;
} elsif ( $ player_data - > { correct_streak } > 10 ) {
print "$color{orange}$nick$color{cyan} IS UNTOUCHABLE WITH A $color{orange}$player_data->{correct_streak}$color{cyan} CORRECT ANSWER STREAK!$color{reset}\n" ;
}
2015-01-28 12:04:28 +01:00
}
2015-01-28 09:40:40 +01:00
$ scores - > update_player_data ( $ player_id , $ player_data ) ;
$ scores - > end ;
2015-01-28 12:04:28 +01:00
2015-05-23 11:27:53 +02:00
$ qstats - > update_question_data ( $ id , $ qdata ) ;
$ qstats - > end ;
2015-01-28 12:04:28 +01:00
unlink "$CJEOPARDY_DATA-$channel" ;
unlink "$CJEOPARDY_HINT-$channel" ;
open $ fh , ">" , "$CJEOPARDY_LAST_ANSWER-$channel" or die "Couldn't open $CJEOPARDY_LAST_ANSWER-$channel: $!" ;
my $ time = scalar gettimeofday ;
print $ fh "$nick\n$data[1]$time\n" ;
close $ fh ;
2015-01-30 06:55:46 +01:00
close $ semaphore ;
2021-07-11 00:00:22 +02:00
if ( $ channel eq '#cjeopardy' or $ channel eq '#c-jeopardy' ) {
2015-01-28 12:04:28 +01:00
my $ question = `./cjeopardy.pl $channel` ;
2019-06-26 18:34:19 +02:00
2015-01-28 12:04:28 +01:00
if ( $ hint_only_mode ) {
2015-05-21 11:25:08 +02:00
my $ hint = `./cjeopardy_hint.pl candide $channel` ;
2015-01-28 12:04:28 +01:00
$ hint =~ s/^Hint: // ;
print "Next hint: $hint\n" ;
} else {
2016-12-07 05:47:24 +01:00
if ( $ question =~ s/^(\[Filter active[^\n]+\n)// ) {
print $ 1 ;
}
2015-01-31 03:40:19 +01:00
print "$color{magneta}Next question$color{reset}: $question\n" ;
2015-01-28 12:04:28 +01:00
}
}
2014-07-28 06:29:05 +02:00
exit ;
}
}
2014-08-31 22:21:09 +02:00
my $ correct_percentage = 100 - $ incorrect_percentage ;
if ( $ correct_percentage >= 80 ) {
2015-02-07 17:47:42 +01:00
printf "Sorry, '$color{red}$text$color{reset}' is %.1f%% correct. So close!" , $ correct_percentage ;
2014-08-31 22:21:09 +02:00
} elsif ( $ correct_percentage >= 70 ) {
2015-02-07 17:47:42 +01:00
printf "Sorry, '$color{red}$text$color{reset}' is %.1f%% correct. Almost." , $ correct_percentage ;
2014-08-31 22:21:09 +02:00
} elsif ( $ correct_percentage >= 50 ) {
2015-02-07 17:47:42 +01:00
printf "Sorry, '$color{red}$text$color{reset}' is only %.1f%% correct." , $ correct_percentage ;
2014-08-31 22:21:09 +02:00
} else {
2015-02-07 17:47:42 +01:00
print "Sorry, '$color{red}$text$color{reset}' is incorrect." ;
2015-01-28 09:40:40 +01:00
}
WRONG_ANSWER:
$ player_data - > { wrong_answers } + + ;
$ player_data - > { lifetime_wrong_answers } + + ;
$ player_data - > { wrong_streak } + + ;
$ player_data - > { last_wrong_timestamp } = scalar gettimeofday ;
2015-02-07 17:47:42 +01:00
if ( $ player_data - > { correct_streak } >= 3 ) {
print " $color{red}You just ended your $color{orange}$player_data->{correct_streak} $color{red}correct answer streak!$color{reset}\n" ;
} else {
print "\n" ;
}
2015-01-28 09:40:40 +01:00
$ player_data - > { correct_streak } = 0 ;
if ( $ player_data - > { wrong_streak } > $ player_data - > { highest_wrong_streak } ) {
$ player_data - > { highest_wrong_streak } = $ player_data - > { wrong_streak } ;
}
if ( $ player_data - > { highest_wrong_streak } > $ player_data - > { lifetime_highest_wrong_streak } ) {
$ player_data - > { lifetime_highest_wrong_streak } = $ player_data - > { highest_wrong_streak } ;
2014-08-31 22:21:09 +02:00
}
2015-01-28 09:40:40 +01:00
2015-05-23 11:27:53 +02:00
$ qdata - > { wrong } + + ;
$ qdata - > { wrong_streak } + + ;
if ( $ qdata - > { wrong_streak } > $ qdata - > { highest_wrong_streak } ) {
$ qdata - > { highest_wrong_streak } = $ qdata - > { wrong_streak } ;
}
2015-05-24 15:17:56 +02:00
$ qstats - > add_wrong_answer ( $ id , $ lctext , $ nick ) ;
2015-05-23 11:27:53 +02:00
2015-01-28 12:04:28 +01:00
my % streaks = (
2016-12-07 02:57:50 +01:00
4 = > "Maybe try asking for a hint, $nick?" ,
2015-05-21 11:25:08 +02:00
5 = > "Guessing, are we, $nick?" ,
7 = > "Think of your correct/incorrect ratio! Use a hint, $nick!" ,
2015-01-28 12:04:28 +01:00
) ;
if ( exists $ streaks { $ player_data - > { wrong_streak } } ) {
print "$streaks{$player_data->{wrong_streak}}$color{reset}\n" ;
}
2015-01-28 09:40:40 +01:00
$ scores - > update_player_data ( $ player_id , $ player_data ) ;
$ scores - > end ;
2015-05-23 11:27:53 +02:00
$ qstats - > update_question_data ( $ id , $ qdata ) ;
$ qstats - > end ;