1# Quiz script for irssi 2# (C) Simon Huggins 2001 3# huggie@earth.li 4 5# This program is free software; you can redistribute it and/or modify it 6# under the terms of the GNU General Public License as published by the Free 7# Software Foundation; either version 2 of the License, or (at your option) 8# any later version. 9# 10# This program is distributed in the hope that it will be useful, but 11# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 12# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 13# for more details. 14# 15# You should have received a copy of the GNU General Public License along 16# with this program; if not, write to the Free Software Foundation, Inc., 59 17# Temple Place, Suite 330, Boston, MA 02111-1307 USA 18# 19# TODO: 20# - Do something when people quit (remove from team, readd when 21# rejoin?) 22# - Store questions asked in a file rather than just in memory so it 23# can be restarted without a problem. 24 25use strict; 26use vars qw($VERSION %IRSSI); 27 28use Irssi 20020217.1542 (); # Version 0.8.1 29$VERSION = "0.8"; 30%IRSSI = ( 31authors => "Simon Huggins", 32contact => "huggie-irssi\@earth.li", 33name => "Quiz", 34description => "Turns irssi into a quiz bot", 35license => "GPLv2", 36url => "http://the.earth.li/~huggie/irssi/", 37changed => "2017-04-03", 38); 39 40use Irssi::Irc; 41use Data::Dumper; 42 43Irssi::settings_add_str("misc","quiz_admin","huggie"); 44Irssi::settings_add_str("misc","quiz_passwd","stuff"); 45Irssi::settings_add_str("misc","quiz_file","/home/huggie/.irssi/questions"); 46 47Irssi::settings_add_int("misc","quiz_qlength",60); 48Irssi::settings_add_int("misc","quiz_hints",4); 49Irssi::settings_add_int("misc","quiz_target_score",10); 50Irssi::settings_add_int("misc","quiz_leave_concealed_chars",1); 51 52Irssi::command("set cmd_queue_speed 2010"); 53 54{ 55my $s; 56 57sub load_questions($$) { 58 my ($game,$force) = @_; 59 my $tag = $game->{'tag'}; 60 my $channel = $game->{'channel'}; 61 62 my $server = Irssi::server_find_tag($tag); 63 64 if (!defined $server) { 65 Irssi::print("Hrm, couldn't find server for tag ($tag) in load_questions"); 66 return; 67 } 68 69 return if $game->{'questions'} and not $force; 70 71 my $file = Irssi::settings_get_str("quiz_file"); 72 if (open(QS, '<',$file)) { 73 @{$game->{'questions'}}=<QS>; 74 close(QS); 75 Irssi::print("Loaded questions"); 76 return 1; 77 } else { 78 $server->command("msg $channel Can't find quiz questions, sorry."); 79 return; 80 } 81} 82 83sub start_game($) { 84 my $game = shift; 85 my $tag = $game->{'tag'}; 86 my $channel = $game->{'channel'}; 87 my $server = Irssi::server_find_tag($tag); 88 89 if (!defined $server) { 90 Irssi::print("Hrm, couldn't find server for tag ($tag) in start_game"); 91 return; 92 } 93 94 Irssi::timeout_remove($game->{'timeouttag'}); 95 undef $game->{'timeouttag'}; 96 97 if (!keys %{$game->{'teams'}}) { 98 $server->command("msg $channel Sorry no one joined!"); 99 $game->{'state'} = "over"; 100 game_over($game); 101 return; 102 } 103 $game->{'state'} = "game"; 104 105 $server->command("msg $channel Game starts now. Questions last ". 106 Irssi::settings_get_int("quiz_qlength"). 107 " seconds and there are ". 108 (Irssi::settings_get_int("quiz_hints")-1). 109 " hints. First to reach ". 110 Irssi::settings_get_int("quiz_target_score")." wins."); 111 next_question($game); 112} 113 114sub show_scores($) { 115 my $game = shift; 116 my $tag = $game->{'tag'}; 117 my $channel = $game->{'channel'}; 118 my $server = Irssi::server_find_tag($tag); 119 120 if (!defined $server) { 121 Irssi::print("Hrm, couldn't find server for tag ($tag) in show_scores"); 122 return; 123 } 124 125 my (@redscorers,@bluescorers); 126 127 foreach my $score (sort keys %{$game->{'scores'}}) { 128 if ($score =~ /^blue/) { 129 $score =~ s/^blue//; 130 push @bluescorers, "$score(". 131 $game->{'scores'}->{"blue".$score}.")"; 132 } else { 133 $score =~ s/^red//; 134 push @redscorers, "$score(". 135 $game->{'scores'}->{"red".$score}.")"; 136 } 137 } 138 139 $server->command("msg $channel 12Blue: ".$game->{'bluescore'} 140 ." ".join(",",@bluescorers)); 141 $server->command("msg $channel 4Red : ".$game->{'redscore'} 142 ." ".join(",",@redscorers)); 143 144 my $ts = Irssi::settings_get_int("quiz_target_score"); 145 if ($game->{'bluescore'} == $ts or $game->{'redscore'} == $ts) { 146 if ($game->{'bluescore'} == $ts) { 147 $server->command("msg $channel 12Blue team wins ". 148 $game->{'bluescore'}." to ". 149 $game->{'redscore'}); 150 } else { 151 $server->command("msg $channel 4Red team wins ". 152 $game->{'redscore'}." to ". 153 $game->{'bluescore'}); 154 } 155 $game->{'state'}="over"; 156 } elsif ($game->{'state'} ne "over") { 157 $game->{'state'}="pause"; 158 $server->command("msg $channel Next question in 20 seconds."); 159 if ($game->{'timeouttag'}) { 160 Irssi::timeout_remove($game->{'timeouttag'}); 161 } 162 $game->{'timeouttag'} = Irssi::timeout_add(20000, 163 "next_question",$game); 164 $game->{'timeout'} = time() + 20; 165 } 166 game_over($game); 167} 168 169sub hint($) { 170 my $game = shift; 171 my $tag = $game->{'tag'}; 172 my $channel = $game->{'channel'}; 173 my $server = Irssi::server_find_tag($tag); 174 175 if (!defined $server) { 176 Irssi::print("Hrm, couldn't find server for tag ($tag) in hint"); 177 return; 178 } 179 180 return if game_over($game); 181 if ($game->{'end'} <= time()) { 182 $server->command("msg $channel Time's up. The answer is: ".$game->{'answer'}); 183 show_scores($game); 184 } else { 185 $game->{'hint'}++; 186 my $num = $game->{'current_answer'} =~ s/\*/*/g; 187 if ($num <= Irssi::settings_get_int("quiz_leave_concealed_chars")) { 188 return; 189 } 190 my $pos = index($game->{'current_answer'},"*"); 191 if ($pos >= 0) { 192 $game->{'current_answer'} =~ s/\*/substr($game->{'answer'},$pos,1)/e; 193 } 194 my $hinttime = $game->{'hint'}*$game->{'hintlen'}; 195 if ($hinttime != int($hinttime)) { 196 $hinttime = sprintf("%.2f", $hinttime); 197 } 198 $server->command("msg $channel $hinttime second hint: ". 199 $game->{'current_answer'}); 200 } 201} 202 203sub game_over($) { 204 my $game = shift; 205 my $tag = $game->{'tag'}; 206 my $channel = $game->{'channel'}; 207 my $server = Irssi::server_find_tag($tag); 208 209 if (!defined $server) { 210 Irssi::print("Hrm, couldn't find server for tag ($tag) in game_over"); 211 return; 212 } 213 214 if ($game->{'state'} eq "over") { 215 Irssi::timeout_remove($game->{'timeouttag'}); 216 undef $game->{'timeouttag'}; 217 undef $game->{'state'}; 218 undef $game->{'teams'}; 219 undef $game->{'scores'}; 220 $server->command("msg $channel Trivia is disabled. Use !trivon to restart."); 221 return 1; 222 } 223 return; 224} 225 226sub next_question($) { 227 my $game = shift; 228 my $tag = $game->{'tag'}; 229 my $channel = $game->{'channel'}; 230 my $server = Irssi::server_find_tag($tag); 231 232 if (!defined $server) { 233 Irssi::print("Hrm, couldn't find server for tag ($tag) in next_question"); 234 return; 235 } 236 237 my $len = Irssi::settings_get_int("quiz_qlength")/ 238 Irssi::settings_get_int("quiz_hints"); 239 if ($game->{'timeouttag'}) { 240 Irssi::timeout_remove($game->{'timeouttag'}); 241 } 242 $game->{'timeouttag'} = Irssi::timeout_add($len*1000, "hint",$game); 243 my $t = time(); 244 $game->{'timeout'} = $t + $len; 245 $game->{'end'} = Irssi::settings_get_int("quiz_qlength")+$t; 246 $game->{'hint'}=0; 247 $game->{'hintlen'} = $len; 248 if (!@{$game->{'questions'}}) { 249 load_questions($game,1); 250 if (!$game->{'questions'}) { 251 $server->command("msg $channel Hmmm, no questions found sorry"); 252 $game->{'state'}="over"; 253 } 254 Irssi::print("Questions looped"); 255 } 256 return if game_over($game); 257 my $q = splice(@{$game->{'questions'}},rand(@{$game->{'questions'}}),1); 258 chomp $q; 259 $q =~ s/ 260//; 261 ($game->{'answer'} = $q) =~ s/^(.*)\|//; 262 $server->command("msg $channel Question: $1"); 263 ($game->{'current_answer'} = $game->{'answer'}) =~ s/[a-zA-Z0-9]/*/g; 264 $q = s/^(.*)\|.*?$/$1/; 265 $server->command("msg $channel Answer: ".$game->{'current_answer'}); 266 $game->{'state'}="question"; 267} 268 269sub invite_join($$) { 270 my ($server,$channel) = @_; 271 my $game = $s->{$server->{'tag'}}->{$channel}; 272 273 $server->command("msg $channel Team Trivia thingummie v($VERSION) starts in 1 minute. Type 4!join red or 12!join blue"); 274 $game->{'timeouttag'} = Irssi::timeout_add(60000,"start_game",$game); 275 $game->{'timeout'} = time()+60; 276} 277 278sub secstonormal($) { 279 my $seconds = shift; 280 my ($m,$s); 281 282 $s = $seconds % 60; 283 $m = ($seconds - $s)/60; 284 return sprintf("%02d:%02d",$m,$s); 285} 286 287sub do_pubcommand($$$$) { 288 my ($command,$channel,$server,$nick) = @_; 289 my $game = $s->{$server->{'tag'}}->{$channel}; 290 291 $command = lc $command; 292 $command =~ s/\s*$//; 293 294 if ($command =~ /^!bang$/) { 295 $server->command("msg $channel Dumping..."); 296 foreach (split /\n/,Dumper($s)) { 297 Irssi::print("$_"); 298 } 299 } elsif ($command =~ /^!trivon$/) { 300 if ($s->{$server->{'tag'}}->{$channel}) { 301 if ($s->{$server->{'tag'}}->{$channel}->{'state'}) { 302 $server->command("msg $nick Trivia is already on. Use !trivoff to remove it."); 303 return; 304 } 305 #undef $s->{$server->{'tag'}}->{$channel}; 306 } else { 307 # create structure magically 308 $game = $s->{$server->{'tag'}}->{$channel} = {}; 309 $game->{'tag'} = $server->{'tag'}; 310 $game->{'channel'} = $channel; 311 } 312 $game->{'teams'}={}; 313 $game->{'redscore'} = 0; 314 $game->{'bluescore'} = 0; 315 load_questions($game,0); 316 $game->{'state'} = "join"; 317 invite_join($server,$channel); 318 } elsif ($command =~ /^!trivoff$/) { 319 return if !$game->{'state'}; 320 $game->{'state'}="over"; 321 game_over($game); 322 } elsif ($command =~ /^!join/) { 323 if ($command =~ /^!join (red|blue)$/) { 324 return if !$game->{'state'}; 325 $game->{'teams'}->{$nick}=$1; 326 if ($1 eq "blue") { 327 $server->command("msg $nick You have joined the 12Blue team"); 328 } else { 329 $server->command("msg $nick You have joined the 4Red team"); 330 } 331 } 332 } elsif ($command =~ /^!teams/) { 333 return if !$game->{'state'}; 334 my @blue=(); 335 my @red=(); 336 foreach (sort keys %{$game->{'teams'}}) { 337 push @blue, $_ if $game->{'teams'}->{$_} eq "blue"; 338 push @red, $_ if $game->{'teams'}->{$_} eq "red"; 339 } 340 $server->command("msg $channel 12Blue: ".join(",",@blue)); 341 $server->command("msg $channel 4Red : ".join(",",@red)); 342 } elsif ($command =~ /^!timeleft$/) { 343 if ($game->{'state'} eq "join" and $game->{'timeout'}) { 344 my $diff = $game->{'timeout'} - time(); 345 if ($diff > 0) { 346 $server->command("msg $channel Time left: ".secstonormal($diff)); 347 } else { 348 Irssi::print("Timeleft: $diff ??"); 349 } 350 } 351 } 352} 353 354sub do_command($$$) { 355 my ($command,$nick,$server) = @_; 356 357 $command = lc $command; 358 $command =~ s/\s*$//; 359 360 if ($command =~ /^!bang$/) { 361 $server->command("msg $nick BOOM!"); 362 } elsif ($command =~ /^admin/) { 363 if ($command !~ /^admin (.*)$/) { 364 $server->command("msg $nick admin needs a nick to change the admin user to!"); 365 } else { 366 Irssi::settings_remove("quiz_admin"); 367 Irssi::settings_add_str("misc","quiz_admin",$1); 368 $server->command("msg $nick admin user is now $1"); 369 } 370 } else { 371 $server->command("msg $nick Unknown command '$command'"); 372 } 373} 374 375sub check_answer($$$$) { 376 my ($server,$channel,$nick,$text) = @_; 377 my $game = $s->{$server->{'tag'}}->{$channel}; 378 379 return if not exists $game->{'teams'}->{$nick}; 380 381 $text =~ s/\s*$//; 382 383 if (lc $text eq lc $game->{'answer'}) { 384 $server->command("msg $channel Correct answer by ". 385 ($game->{'teams'}->{$nick} eq "blue"?"12":"4"). 386 $nick.": ".$game->{'answer'}); 387 $game->{'state'}="won"; 388 $game->{$game->{'teams'}->{$nick}."score"}++; 389 $game->{'scores'}->{$game->{'teams'}->{$nick}.$nick}++; 390 show_scores($game); 391 return; 392 } 393 394 my $show=0; 395 my @chars = split //,$text; 396 397 for (my $i=0; $i<length($game->{'answer'}); $i++) { 398 if (lc $chars[$i] eq lc substr($game->{'answer'},$i,1)) { 399 $show = 1 if substr($game->{'current_answer'},$i,1) 400 eq "*"; 401 substr($game->{'current_answer'},$i,1) = 402 substr($game->{'answer'},$i,1); 403 } 404 } 405 $server->command("msg $channel Answer: ".$game->{'current_answer'}) 406 if $show; 407} 408 409sub event_privmsg { 410 my ($server,$data,$nick,$address) = @_; 411 my ($target, $text) = split / :/,$data,2; 412 my ($command); 413 414 if ($target =~ /^#/) { 415 my $game = $s->{$server->{'tag'}}->{$target}; 416 if ($text =~ /^!/) { 417 do_pubcommand($text,$target,$server,$nick); 418 } elsif ($game->{'state'} eq "question") { 419 check_answer($server,$target,$nick,$text); 420 } 421 } else { 422 if ($nick ne Irssi::settings_get_str("quiz_admin")) { 423 my ($passwd); 424 ($passwd, $command) = split /\s/,$text,2; 425 if ($passwd ne Irssi::settings_get_str("quiz_passwd")) { 426 Irssi::print("$nick tried to do $command but got the password wrong."); 427 } 428 } else { 429 $command = $text; 430 } 431 do_command($command,$nick,$server); 432 } 433} 434 435sub event_changed_nick { 436 my ($channel,$nick,$oldnick) = @_; 437 my $server = $channel->{'server'}; 438 my $game = $s->{$server->{'tag'}}->{$channel->{'name'}}; 439 440 return if !$game->{'state'}; 441 442 my $nicktxt = $nick->{'nick'}; 443 if ($game->{'teams'}->{$oldnick}) { 444 $game->{'teams'}->{$nicktxt} = $game->{'teams'}->{$oldnick}; 445 delete $game->{'teams'}->{$oldnick}; 446 } 447} 448 449} 450 451Irssi::signal_add_last("event privmsg", "event_privmsg"); 452Irssi::signal_add("nicklist changed", "event_changed_nick"); 453