1#!/usr/local/bin/perl -T
2# Quizgr script for irssi with "KAOS" questions enabled, modified for greek too
3# copyright Athanasius Emilius Arvanitis
4# arvan@kronos.eng.auth.gr
5# based on quiz.pl version 0.7
6# Quiz script for irssi
7# (C) Simon Huggins 2001
8# huggie@earth.li
9
10# This program is free software; you can redistribute it and/or modify it
11# under the terms of the GNU General Public License as published by the Free
12# Software Foundation; either version 2 of the License, or (at your option)
13# any later version.
14#
15# This program is distributed in the hope that it will be useful, but
16# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
17# or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
18# for more details.
19#
20# You should have received a copy of the GNU General Public License along
21# with this program; if not, write to the Free Software Foundation, Inc., 59
22# Temple Place, Suite 330, Boston, MA 02111-1307  USA
23#
24# DONE:
25#       - support for many answers (not alternate though) per question
26#       - support for hellenic (aka greek)
27#       - remembers the questions that were answered (stores them in
28#       ./wr/used_questions)
29#       - if nobody says anything for a period of time, game ends
30#       - added !repeat to repeat the question
31#       - it wont crash if you smile!
32# TODO:
33#       - known bug: sometimes it ignores some people (they cant join etc).
34#	if you join #CHANNEL and the bot is on #channel forget it...
35#       - fix kaos hints
36#       - if we have kaos, there should be more time to answer
37#       - CLEAN up the code
38# 	- Do something when people quit (remove from team, readd when rejoin?)
39
40use strict;
41use vars qw($VERSION %IRSSI);
42
43use Irssi 20020217.1542 (); # Version 0.8.1 or perhaps get the most up to date irssi version
44$VERSION = "0.7GR03";
45%IRSSI = (
46authors     => "Athanasius Emilius Arvanitis based on Simon Huggins quiz 0.7",
47contact     => "arvan",
48name        => "Quizgr",
49description => "Turns irssi into a quiz bot. Has greek language and many answers support",
50license     => "GPLv2",
51url         => "http://kronos.eng.auth.gr/~arvan/irssi/",
52changed     => "Tue Nov 26 13:37:59 EET 2002",
53);
54
55use Irssi::Irc;
56use Data::Dumper;
57
58Irssi::settings_add_str("misc","quiz_admin","jbg");
59Irssi::settings_add_str("misc","quiz_passwd","stuff");
60Irssi::settings_add_str("misc","quiz_file","$ENV{HOME}/.irssi/scripts/autorun/gr_quiz_questions");
61Irssi::settings_add_str("misc","used_file","$ENV{HOME}/.irssi/scripts/autorun/wr/used_questions");
62
63Irssi::settings_add_int("misc","quiz_qlength",70);
64Irssi::settings_add_int("misc","quiz_hints",7);
65Irssi::settings_add_int("misc","quiz_target_score",50);
66Irssi::settings_add_int("misc","quiz_leave_concealed_chars",1);
67
68Irssi::command("set cmd_queue_speed 2010");
69
70{
71# when warnings used $s complains
72my $s;
73my $answerBAKforCHAOS;
74
75sub load_questions($$) {
76	my ($game,$force) = @_;
77	my $tag = $game->{'tag'};
78	my $channel = $game->{'channel'};
79
80	$game->{'used_questions'}=[];
81
82	my $server = Irssi::server_find_tag($tag);
83
84	if (!defined $server) {
85		Irssi::print("Hrm, couldn't find server for tag ($tag) in load_questions");
86		return;
87	}
88
89	return if $game->{'questions'} and not $force;
90
91	#the next must be checked
92
93	my $file = Irssi::settings_get_str("quiz_file");
94	if (open(QS, "<", $file)) { #open for QS
95		@{$game->{'questions'}}=sort <QS>;
96		close(QS);
97		Irssi::print("Loaded questions");
98
99		my $file2 = Irssi::settings_get_str("used_file");
100		if (open(QS2, "<", $file2)) { #open for QS2
101			@{$game->{'used_questions'}}=sort <QS2>;
102			close(QS2);
103
104			#from perlfaq copy paste
105			@{$game->{'intersection'}} = @{$game->{'difference'}} = ();
106			%{$game->{'count'}} = ();
107
108			my $element;
109			foreach $element (@{$game->{'questions'}}, @{$game->{'used_questions'}}) { ${$game->{'count'}}{$element}++ };
110
111			foreach $element (keys %{$game->{'count'}}) { #open foreach
112				push @{ ${$game->{'count'}}{$element} > 1 ? \@{$game->{'intersection'}} : \@{$game->{'difference'}} }, $element;
113				} #close foreach
114
115			my $ts = Irssi::settings_get_int("quiz_target_score");
116			my $qCounter=@{$game->{'questions'}};
117			${$game->{'usedCounter'}}=@{$game->{'used_questions'}};
118
119			my $qGOT=($qCounter - ${$game->{'usedCounter'}});
120			my $qNEEDED=(2*($ts)+12);
121			Irssi::print("${$game->{'usedCounter'}} used out of $qCounter total questions");
122
123			if ( $qGOT >= $qNEEDED ) {
124				@{$game->{'questions'}}=@{$game->{'difference'}};
125				Irssi::print("Loaded not used questions");
126				return 1;#used
127				}
128
129			if ( $qGOT < $qNEEDED ) {
130				@{$game->{'used_questions'}}=();
131				Irssi::print("Clearing used questions");
132				return 1;#used
133				}
134		} #close QS2
135
136		return 1;#questions
137		} #close QS
138
139	  else {
140		$server->command("msg $channel Can't find quiz questions, sorry.");
141		return;
142		}
143
144
145
146}
147
148sub start_game($) {
149	my $game = shift;
150	my $tag = $game->{'tag'};
151	my $channel = $game->{'channel'};
152	my $server = Irssi::server_find_tag($tag);
153
154	if (!defined $server) {
155		Irssi::print("Hrm, couldn't find server for tag ($tag) in start_game");
156		return;
157		}
158
159	Irssi::timeout_remove($game->{'timeouttag'});
160	undef $game->{'timeouttag'};
161
162	if (!keys %{$game->{'teams'}}) {
163		$server->command("msg $channel Sorry no one joined!");
164		$game->{'state'} = "over";
165		game_over($game);
166		return;
167		}
168
169	$game->{'state'} = "game";
170
171	$server->command("msg $channel Game starts now. Questions last ".
172		Irssi::settings_get_int("quiz_qlength").
173		" seconds and there are ".
174		(Irssi::settings_get_int("quiz_hints")-1).
175		" hints.  First to reach ".
176		Irssi::settings_get_int("quiz_target_score")." wins.");
177	next_question($game);
178}
179
180sub show_scores($) {
181	my $game = shift;
182	my $tag = $game->{'tag'};
183	my $channel = $game->{'channel'};
184	my $server = Irssi::server_find_tag($tag);
185
186	if (!defined $server) {
187		Irssi::print("Hrm, couldn't find server for tag ($tag) in show_scores");
188		return;
189		}
190
191	my (@redscorers,@bluescorers);
192
193	foreach my $score (sort keys %{$game->{'scores'}}) {
194		if ($score =~ /^blue/) {
195			$score =~ s/^blue//;
196			push @bluescorers, "$score(".
197				$game->{'scores'}->{"blue".$score}.")";
198		} else {
199			$score =~ s/^red//;
200			push @redscorers, "$score(".
201				$game->{'scores'}->{"red".$score}.")";
202		}
203	}
204
205	$server->command("msg $channel 12Blue: ".$game->{'bluescore'}
206		."  ".join(",",@bluescorers));
207	$server->command("msg $channel 4Red: ".$game->{'redscore'}
208		."  ".join(",",@redscorers));
209
210	my $ts = Irssi::settings_get_int("quiz_target_score");
211
212	if ($game->{'bluescore'} >= $ts or $game->{'redscore'} >= $ts) {
213		if ($game->{'bluescore'} > $game->{'redscore'}) {
214			$server->command("msg $channel 12Blue team wins ".
215				$game->{'bluescore'}." to ".
216				$game->{'redscore'});
217		} else {
218			$server->command("msg $channel 4Red team wins ".
219				$game->{'redscore'}." to ".
220				$game->{'bluescore'});
221		}
222		$game->{'state'}="over";
223	} elsif ($game->{'state'} ne "over") {
224		$game->{'state'}="pause";
225		$server->command("msg $channel Next question in 6 20 seconds.");
226		if ($game->{'timeouttag'}) {
227			Irssi::timeout_remove($game->{'timeouttag'});
228		}
229		$game->{'timeouttag'} = Irssi::timeout_add(20000,
230			"next_question",$game);
231		$game->{'timeout'} = time() + 20;
232	}
233	game_over($game);
234}
235
236sub hint($) {
237	my $game = shift;
238	my $tag = $game->{'tag'};
239	my $channel = $game->{'channel'};
240	my $server = Irssi::server_find_tag($tag);
241
242	if (!defined $server) {
243		Irssi::print("Hrm, couldn't find server for tag ($tag) in hint");
244		return;
245		}
246
247	return if game_over($game);
248	if ($game->{'end'} <= time()) {
249		$server->command("msg $channel Time's up.  The answer is: 2  ".$game->{'answer'});
250		show_scores($game);
251	} else {
252		$game->{'hint'}++;
253		my $num = $game->{'current_answer'} =~ s/\*/*/g;
254		if ($num <= Irssi::settings_get_int("quiz_leave_concealed_chars")) {
255			return;
256			}
257
258		my $pos = index($game->{'current_answer'},"*");
259		if ($pos >= 0) {
260			#$game->{'current_answer'} =~ s/\*/substr($game->{'answer'},$pos,1)/e;
261			$game->{'current_answer'} =~ s/\*/substr($answerBAKforCHAOS,$pos,1)/e;
262			}
263
264		my $hinttime = $game->{'hint'}*$game->{'hintlen'};
265		if ($hinttime != int($hinttime)) {
266			$hinttime = sprintf("%.2f", $hinttime);
267			}
268		$server->command("msg $channel 2  $hinttime second hint: 6  ".
269			$game->{'current_answer'});
270	} #else end
271}
272
273sub game_over($) {
274	my $game = shift;
275	my $tag = $game->{'tag'};
276	my $channel = $game->{'channel'};
277	my $server = Irssi::server_find_tag($tag);
278
279	if (!defined $server) {
280		Irssi::print("Hrm, couldn't find server for tag ($tag) in game_over");
281		return;
282	}
283
284	if ($game->{'state'} eq "over") {
285		Irssi::timeout_remove($game->{'timeouttag'});
286		undef $game->{'timeouttag'};
287		undef $game->{'state'};
288		undef $game->{'teams'};
289		undef $game->{'scores'};
290
291                #save used questions
292                my $file2 = Irssi::settings_get_str("used_file");
293                if (open(QS2, ">", $file2)) {
294                        my $line;
295			@{$game->{'used_questions'}}=sort @{$game->{'used_questions'}};
296                        foreach $line (@{$game->{'used_questions'}}){
297                                print QS2 $line ;
298                                }
299                        close(QS2);
300                        Irssi::print("Saved used questions");
301                        }
302
303
304		$server->command("msg $channel Trivia is disabled.  Use !start or !trivon to restart.");
305		return 1;
306	}
307	return;
308}
309
310sub next_question($) {
311	my $game = shift;
312	my $tag = $game->{'tag'};
313	my $channel = $game->{'channel'};
314	my $server = Irssi::server_find_tag($tag);
315
316	if (!defined $server) {
317		Irssi::print("Hrm, couldn't find server for tag ($tag) in next_question");
318		return;
319	}
320
321	#check previous text time and
322	#if noone says anything for 180 seconds end game
323	if (defined $game->{'time_last_text'}) {
324		my $diff2=time() - $game->{'time_last_text'};
325		if ( $diff2 > 180) {
326			$game->{'state'}="over";
327		}
328	}
329
330
331	my $len = Irssi::settings_get_int("quiz_qlength")/
332		Irssi::settings_get_int("quiz_hints");
333	if ($game->{'timeouttag'}) {
334		Irssi::timeout_remove($game->{'timeouttag'});
335	}
336	$game->{'timeouttag'} = Irssi::timeout_add($len*1000, "hint",$game);
337	my $t = time();
338	$game->{'timeout'} = $t + $len;
339	$game->{'end'} = Irssi::settings_get_int("quiz_qlength")+$t;
340	$game->{'hint'}=0;
341	$game->{'hintlen'} = $len;
342	if (!@{$game->{'questions'}}) {
343		load_questions($game,1);
344		if (!$game->{'questions'}) {
345			$server->command("msg $channel Hmmm, no questions found sorry");
346			$game->{'state'}="over";
347		}
348		Irssi::print("Questions looped");
349	}
350	return if game_over($game);
351
352	#random question
353	${$game->{'randIDX'}}= @{$game->{'questions'}};
354	${$game->{'randIDX'}}=rand(${$game->{'randIDX'}});
355	my $q = ${$game->{'questions'}}[${$game->{'randIDX'}}];
356	${$game->{'the_question'}} = $q;
357
358	#removing it from the questions
359	splice (@{$game->{'questions'}}, ${$game->{'randIDX'}}, 1);
360
361	#see faq for splice/random may be bad
362	#my $q = splice(@{$game->{'questions'}},rand(@{$game->{'questions'}}),1);
363	chomp $q;
364	$q =~ s///;
365	#($game->{'answer'} = $q) =~ s/^(.*)\|//;
366
367	($game->{'question'}, $game->{'answer'}) = split(/\|/, $q,2);
368	$answerBAKforCHAOS = $game->{'answer'};
369	if ( $game->{'answer'} =~ /\|/ )
370		{ $server->command("msg $channel KAOS CHAOS ����!!!");
371			$game->{'answer'} = $game->{'answer'}."|";
372
373		}
374	$server->command("msg $channel 13Question: 10 $game->{'question'} ");
375	#added �-��-� so it can hide greek too
376	($game->{'current_answer'} = $game->{'answer'}) =~ s/[a-zA-Z0-9�-��-�]/*/g;
377	#$q = s/^(.*)\|.*?$/$1/;
378	$server->command("msg $channel Answer:  ".$game->{'current_answer'});
379	$game->{'state'}="question";
380}
381
382sub invite_join($$) {
383	my ($server,$channel) = @_;
384	my $game = $s->{$server->{'tag'}}->{$channel};
385
386	$server->command("msg $channel Team Trivia thingummie v($VERSION) starts in 1 minute.  Type 4!join red or 12!join blue");
387	$game->{'timeouttag'} = Irssi::timeout_add(60000,"start_game",$game);
388	$game->{'timeout'} = time()+60;
389}
390
391sub secstonormal($) {
392	my $seconds = shift;
393	my ($m,$s);
394
395	$s = $seconds % 60;
396	$m = ($seconds - $s)/60;
397	return sprintf("%02d:%02d",$m,$s);
398}
399
400sub do_pubcommand($$$$) {
401	my ($command,$channel,$server,$nick) = @_;
402	my $game = $s->{$server->{'tag'}}->{$channel};
403
404	$command = lc $command;
405	$command =~ s/\s*$//;
406
407	if ($command =~ /^!bang$/) {
408		$server->command("msg $channel Dumping...");
409		foreach (split /\n/,Dumper($s)) {
410			Irssi::print("$_");
411		}
412	} elsif ($command =~ /^!trivon$|^!start$|!����$/) {
413		if ($s->{$server->{'tag'}}->{$channel}) {
414			if ($s->{$server->{'tag'}}->{$channel}->{'state'}) {
415				$server->command("msg $nick Trivia is already on.  Use !trivoff or !stop to remove it.");
416				return;
417			}
418			#undef $s->{$server->{'tag'}}->{$channel};
419		} else {
420			# create structure magically
421			$game = $s->{$server->{'tag'}}->{$channel} = {};
422			$game->{'tag'} = $server->{'tag'};
423			$game->{'channel'} = $channel;
424		}
425		$game->{'teams'}={};
426		$game->{'redscore'} = 0;
427		$game->{'bluescore'} = 0;
428		load_questions($game,0);
429		$game->{'state'} = "join";
430		invite_join($server,$channel);
431	} elsif ($command =~ /^!trivoff$|^!stop$|!������$/) {
432		return if !$game->{'state'};
433		$game->{'state'}="over";
434		game_over($game);
435	} elsif ($command =~ /^!join/) {
436		if ($command =~ /^!join (red|blue)$/) {
437			return if !$game->{'state'};
438			$game->{'teams'}->{$nick}=$1;
439			if ($1 eq "blue") {
440				$server->command("notice $nick You have joined the 12Blue team");
441			} else {
442				$server->command("notice $nick You have joined the 4Red team");
443			}
444		}
445	} elsif ($command =~ /^!teams/) {
446		return if !$game->{'state'};
447		my @blue=();
448		my @red=();
449		foreach (sort keys %{$game->{'teams'}}) {
450			push @blue, $_ if $game->{'teams'}->{$_} eq "blue";
451			push @red,  $_ if $game->{'teams'}->{$_} eq "red";
452		}
453		$server->command("msg $channel 12Blue: ".join(",",@blue));
454		$server->command("msg $channel 4Red : ".join(",",@red));
455	} elsif ($command =~ /^!repeat$/) {
456		return if !$game->{'state'};
457		$server->command("msg $channel Question is $game->{'question'}");
458	} elsif ($command =~ /^!timeleft$/) {
459		if ($game->{'state'} eq "join" and $game->{'timeout'}) {
460			my $diff = $game->{'timeout'} - time();
461			if ($diff > 0) {
462				$server->command("msg $channel Time left: ".secstonormal($diff));
463			} else {
464				Irssi::print("Timeleft: $diff ??");
465			}
466		}
467	}
468}
469
470sub do_command($$$) {
471	my ($command,$nick,$server) = @_;
472
473	$command = lc $command;
474	$command =~ s/\s*$//;
475
476	if ($command =~ /^!bang$/) {
477		$server->command("msg $nick BOOM!");
478	} elsif ($command =~ /^admin/) {
479		if ($command !~ /^admin (.*)$/) {
480			$server->command("msg $nick admin needs a nick to change the admin user to!");
481		} else {
482			Irssi::settings_remove("quiz_admin");
483			Irssi::settings_add_str("misc","quiz_admin",$1);
484			$server->command("msg $nick admin user is now $1");
485		}
486	} else {
487		#$server->command("msg $nick Unknown command '$command'");
488	}
489}
490
491#check check_answer for bad { }
492
493sub check_answer($$$$) {
494	my ($server,$channel,$nick,$text) = @_;
495	my $game = $s->{$server->{'tag'}}->{$channel};
496
497	return if not exists $game->{'teams'}->{$nick};
498
499	#if $text exist check time and remembers it for end-game
500	if (defined $text) {
501		$game->{'time_last_text'} = time();
502		}
503
504
505	$text =~ s/\s*$//;
506	$text =~ s/^ //;
507	$text =~ s/ $//;
508
509	#from cgi input-purify / try without it and it will crash with :(
510	#dont know if it needs em all, may check it in future
511
512	if ($text =~ s/([\&;\`'\\\|"*?~<>^\(\)\[\]\{\}\$\n\r])/\\$1/g)
513		{ $text="abcdef";
514		}
515	# is the above the reason it didnt join?
516
517	#if greek supports troubles you comment the next
518	$text =~ y/����������������ڼ�ۿ������������������������/���������������������������������������������/;
519
520	my $answerNOtonos = lc $game->{'answer'};
521	#if greek supports troubles you comment the next
522	$answerNOtonos =~ y/����������������ڼ�ۿ������������������������/���������������������������������������������/;
523
524	if ( $answerNOtonos =~ /\|/) {
525
526			if ( ($answerNOtonos =~ /\|$text\|/)||($answerNOtonos =~ /^$text\|/) )
527			{
528			$answerNOtonos =~ s/$text\|//;
529
530	 		$game->{'answer'}=$answerNOtonos;
531
532
533			$server->command("msg $channel  2Correct answer by ".
534			($game->{'teams'}->{$nick} eq "blue"?"12":"4").
535			 $nick.": ".$text);
536
537			$game->{$game->{'teams'}->{$nick}."score"}++;
538			$game->{'scores'}->{$game->{'teams'}->{$nick}.$nick}++;
539
540			if ($answerNOtonos eq "") {
541
542        			#putting it in used
543				if (@{$game->{'used_questions'}}){
544        			${$game->{'usedCounter'}} = @{$game->{'used_questions'}};
545				} else {${$game->{'usedCounter'}}=0;}
546
547        			${$game->{'used_questions'}}[${$game->{'usedCounter'}}]=${$game->{'the_question'}};
548
549				$game->{'state'}="won";
550
551				$server->command("msg $channel 2  $answerBAKforCHAOS") ;
552				show_scores($game);
553				return;
554				}
555
556			#show_scores($game);
557			}
558	}
559
560
561                elsif (( $answerNOtonos !~ /\|/) && (lc $text eq $answerNOtonos))  {
562
563			$server->command("msg $channel 2Correct answer by ".
564			($game->{'teams'}->{$nick} eq "blue"?"12":"4").
565			$nick.": ".$game->{'answer'});
566			$game->{'state'}="won";
567
568        			#putting it in used
569				if (@{$game->{'used_questions'}}){
570        			${$game->{'usedCounter'}} = @{$game->{'used_questions'}};
571				} else {${$game->{'usedCounter'}} =0;}
572
573        			${$game->{'used_questions'}}[${$game->{'usedCounter'}}]=${$game->{'the_question'}};
574
575			$game->{$game->{'teams'}->{$nick}."score"}++;
576			$game->{'scores'}->{$game->{'teams'}->{$nick}.$nick}++;
577			show_scores($game);
578			return;
579		}
580
581
582
583	my $show=0;
584	my @chars = split //,$text;
585
586	for (my $i=0; $i<length($game->{'answer'}); $i++) {
587		if (lc $chars[$i] eq lc substr($game->{'answer'},$i,1)) {
588			$show = 1 if substr($game->{'current_answer'},$i,1)
589				eq "*";
590			substr($game->{'current_answer'},$i,1) =
591				substr($game->{'answer'},$i,1);
592		}
593	}
594
595	$server->command("msg $channel Answer: ".$game->{'current_answer'})
596		if $show;
597}
598
599
600
601sub event_privmsg {
602	my ($server,$data,$nick,$address) = @_;
603	my ($target, $text) = split / :/,$data,2;
604	my ($command);
605
606	if ($target =~ /^#/) {
607		my $game = $s->{$server->{'tag'}}->{$target};
608		if ($text =~ /^!/) {
609			do_pubcommand($text,$target,$server,$nick);
610		} elsif ($game->{'state'} eq "question") {
611			check_answer($server,$target,$nick,$text);
612		}
613	} else {
614		if ($nick ne Irssi::settings_get_str("quiz_admin")) {
615			my ($passwd);
616			($passwd, $command) = split /\s/,$text,2;
617			if ($passwd ne Irssi::settings_get_str("quiz_passwd")) {
618				#Irssi::print("$nick tried to do $command but got the password wrong.");
619				Irssi::print("$nick got the password wrong.");
620			}
621		} else {
622			$command = $text;
623		}
624		do_command($command,$nick,$server);
625	}
626}
627
628sub event_changed_nick {
629	my ($channel,$nick,$oldnick) = @_;
630	my $server = $channel->{'server'};
631	my $game = $s->{$server->{'tag'}}->{$channel->{'name'}};
632
633	return if !$game->{'state'};
634
635	my $nicktxt = $nick->{'nick'};
636	if ($game->{'teams'}->{$oldnick}) {
637		$game->{'teams'}->{$nicktxt} = $game->{'teams'}->{$oldnick};
638		delete $game->{'teams'}->{$oldnick};
639		}
640
641}
642
643
644}
645
646Irssi::signal_add_last("event privmsg", "event_privmsg");
647# Irssi::signal_add_last("massjoin", "sig_massjoin");
648#Irssi::signal_add_last("message nick", "on_nick"); #when /nick
649#Irssi::signal_add_last("message part", "on_part");
650#Irssi::signal_add_last("message join", "on_join");
651#Irssi::signal_add_last("message quit", "on_quit");
652
653# Channel::nicks(channel) Return a list of all nicks in channel.
654
655Irssi::signal_add("nicklist changed", "event_changed_nick");
656